Вызов производного метода для элемента базового вектора (приведенный пример) - PullRequest
2 голосов
/ 08 декабря 2010

Предположим, что у меня есть следующая структура классов. Я хочу иметь возможность определить, к какому типу класса относится элемент в моем векторе 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();
}

Ответы [ 4 ]

1 голос
/ 08 декабря 2010

В идеале вы должны добавить виртуальную функцию в базовый класс 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, для автоматического управления ресурсами.

0 голосов
/ 08 декабря 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 имеет то преимущество, что он встроен в язык и не требует никаких усилий с вашей стороны для поддержки. Использование собственной функции имеет более предсказуемые характеристики производительности в широком диапазоне компиляторов и позволяет хранить данные, отличные от того, какой тип объекта на самом деле - но вы все равно должны написать все сами.

0 голосов
/ 08 декабря 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;
}
0 голосов
/ 08 декабря 2010

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

...