Вызов производного метода для элемента базового вектора (приведен пример)

Предположим, что у меня есть следующая структура классов. Я хочу иметь возможность определить, к какому типу класса относится элемент в моем векторе Animal, чтобы я мог выполнять над ним методы, специфичные для подкласса. Пример ниже должен продемонстрировать:

#include <iostream>
#include <vector>

using namespace std;

class Animal {
    public:
    int foodcount;

    Animal() {
        foodcount = 0;
        cout << "An animal was created.\n";
    }
    virtual ~Animal() {
        cout << "An animal was destroyed.\n";
    }
};

class Lion : public Animal {
    public:
    Lion() {
        cout << "A lion was created.\n";
    }
    virtual ~Lion() {
        cout << "A lion was destroyed.\n";
    }
    void chowMeat(int howmuch) {
        foodcount += howmuch;
    }
};

class Butterfly : public Animal {
    public:
    Butterfly() {
        cout << "A butterfly was created.\n";
    }
    virtual ~Butterfly() {
       cout << "A butterfly was destroyed.\n";
    }
    void drinkNectar(int howmuch) {
       foodcount += howmuch;
    }
};

int main() {
    Animal* A = new Lion();
    Animal* B = new Butterfly();
    vector<Animal*> v;

    v.push_back(A);
    v.push_back(B);

    // a little later

    for (int i=0; i<v.size(); i++) {
        if (v[i] is a Lion) v[i]->chowMeat();  // will not work of course
        if (v[i] is a Butterfly) v[i]->drinkNectar();   // will not work of course
    }

    std::cin.get();
    return 0;
}

Очевидно, что отмеченный код не будет работать, но как мне сделать то, что я хочу? Есть ли обходной путь или принцип проектирования, которому я должен следовать, но не делаю? Я изучил dynamic_cast, но понимаю, что это некрасиво. Итак, как мне сделать это правильно?

В Java я бы сделал так:

if (v.get(i).getClass() == Lion.class) {
    ((Lion)v.get(i)).chowMeat();
}
if (v.get(i).getClass() == Butterfly.class) {
    ((Butterfly)v.get(i)).drinkNectar();
}

person Schreib    schedule 08.12.2010    source источник
comment
Зачем вообще делать их подклассом животных, если вы не собираетесь использовать виртуальные методы, чтобы у обоих животных был метод .eat(). Есть ли причина, по которой им нужны разные имена методов, чтобы делать одно и то же? Тогда они не должны наследовать от одного и того же класса.   -  person Falmarri    schedule 08.12.2010
comment
dynamic_cast как это сделать правильно. Это некрасиво, потому что, используя его здесь, вы неявно следуете принципу дизайна, который вы должны. Эквивалент Java непривлекателен по той же причине, и это гораздо более очевидно, учитывая то, как он написан. Кстати, используйте конструкции типа foreach для перебора контейнеров или, по крайней мере, используйте настоящие итераторы вместо индексов. См. также stackoverflow.com/questions/4383250/ .   -  person Karl Knechtel    schedule 08.12.2010
comment
Falmarri: +1 — смысл подтипирования в том, чтобы обобщать. Когда лев ест мясо, а бабочка пьет нектар, они оба питаются сами. Общее описание состоит в том, что это Животные, которые едят. Итак, у нас есть virtual void eat(), и мы реализуем его таким образом, чтобы Лев ел(а), пережевывая мясо, а Бабочка ела(а), выпивая(ла) нектар. (Думаю, вы могли бы вместо этого назвать его feedSelf(), но это излишне неловко. :) )   -  person Karl Knechtel    schedule 08.12.2010


Ответы (4)


В идеале вы должны добавить виртуальную функцию в базовый класс void eat(int quantity) и переопределить эту функцию в производных классах.

В этом случае может даже иметь смысл сделать функцию невиртуальной и реализовать ее в базовом классе, поскольку оба производных класса делают одно и то же.

За исключением этого, вы можете использовать dynamic_cast для проверки динамического типа объекта:

if (Lion* lion = dynamic_cast<Lion*>(v[i])) {
    lion->chowMeat(42); 
}
else if (Butterfly* butterfly = dynamic_cast<Butterfly*>(v[i])) {
    butterfly->drinkNectar(42);
}
// etc.

(С другой стороны, вы должны быть очень осторожны, используя голые указатели в C++; очень сложно написать правильный код, в котором вы управляете ресурсами вручную. В вашем примере вы не освободили объекты, на которые указывают A и B и, таким образом, слили их. Рассмотрите возможность использования интеллектуальных указателей, таких как shared_ptr, для автоматического управления вашими ресурсами.)

person James McNellis    schedule 08.12.2010
comment
Ах, я должен был сделать пример более ясным. Я имею в виду, что методы будут уникальными и будут делать совершенно разные вещи. Но спасибо за ответ dynamic_cast - есть ли другой, более красивый способ его реструктуризации? - person Schreib; 08.12.2010
comment
@Schreib: я не думаю, что это уродливо. Иногда необходимо использовать dynamic_cast, и это не совсем неправильно. Конечно, вы могли бы реализовать свою собственную систему идентификации типов (например, используя функцию базового класса GetType(), которая возвращает уникальный идентификатор, который вы можете использовать для определения типа), но зачем создавать собственную идентификацию типа, когда язык уже встроен в нее? в? - person James McNellis; 08.12.2010
comment
иногда нужно и не совсем неправильно - это абсолютно не защита от обвинения в некрасивости :) - person Karl Knechtel; 08.12.2010
comment
@Карл: Нет, это не так. Я не думаю, что это уродливо, и я пытался упредить фанатиков, выступающих против актеров. - person James McNellis; 08.12.2010

Какова цель цикла? Это потреблять пищу? В этом случае добавьте virtual void consumeFood(int howMuch) в свой базовый класс и переопределите его в своих производных классах.

person EboMike    schedule 08.12.2010

Если пример действительно репрезентативен, виртуальная функция решит вашу насущную проблему гораздо точнее.

В любом случае, если ваши классы имеют виртуальные функции, самый простой ответ — использовать dynamic_cast для проверки того, принадлежит ли объект заданному типу. Например:

for (int i=0; i<v.size(); i++) {
    if (Lion *lion = dynamic_cast<Lion *>(v[i]))
        lion->chowMeat();
    else if(Butterfly *butterfly = dynamic_cast<Butterfly *>(v[i]))
        butterfly->drinkNectar();
}

Он встроен в язык и предназначен только для проверки того, действительно ли указатель на базу указывает на объект более производного типа.

Другой вариант — иметь какую-то виртуальную функцию GetType в вашем базовом классе, которую вы переопределяете для каждого класса, чтобы возвращать что-то (целое число, объект и т. д.), которое однозначно идентифицирует этот класс. Затем вы вызываете эту функцию во время выполнения и проверяете результат, чтобы выяснить, на какой объект указывает указатель.

Преимущество dynamic_cast состоит в том, что он встроен в язык и не требует усилий с вашей стороны для поддержки. Использование собственной функции имеет более предсказуемые характеристики производительности для широкого круга компиляторов и позволяет хранить данные, отличные от того, какой тип действительно имеет объект, но вам все равно придется писать все это самостоятельно.

person Community    schedule 08.12.2010

Почему бы не съесть()?

class Animal {
    public:
    int foodcount;

    Animal() {
        foodcount = 0;
        cout << "An animal was created.\n";
    }
    virtual ~Animal() {
        cout << "An animal was destroyed.\n";
    }
    virtual void eat(int howMuch) {
        foodcount += howmuch;
    }
};

class Lion : public Animal {
    public:
    virtual void eat(int howmuch) {
        Animal::eat(howmuch + 19);
    }
};

class Butterfly : public Animal {
    void eat(int howmuch) {
       Animal::eat(howmuch / 1000);
    }
};

class Tribble: public Animal
{
    void eat(int howmuch) {
       throw DontFeedTribles();
    }
};

int main() {
    std::auto_ptr<Animal> A = new Lion();
    std::auto_ptr<Animal> B = new Butterfly();
    vector<Animal*>  menagerie;

    menagerie.push_back(A.get());
    menagerie.push_back(B.get());

    BOOST_FOREACH(Animal* animal, menagerie)
    {
        animal->eat(10000);
    }

    std::cin.get();
    return 0;
}
person Martin York    schedule 08.12.2010