Полиморфные классы в шаблонах - PullRequest
5 голосов
/ 20 марта 2011

Скажем, у нас есть иерархия классов, в которой у нас есть универсальный класс Animal, который имеет несколько классов, непосредственно наследуемых от него (таких как Dog, Cat, Horse и т. Д.).

При использовании шаблонов в этой иерархии наследования законно ли просто использовать SomeTemplateClass<Animal>, а затем вставлять в Dogs and Cats and Horses этот шаблонный объект?

Например, предположим, что у нас есть шаблонный Stack класс, в котором мы хотим разместить всех видов животных.Могу ли я просто заявить Stack<Animal> s; Dog d; s.push(d); Cat c; s.push(c);

Ответы [ 4 ]

5 голосов
/ 20 марта 2011

Ответ на ваш вопрос, если нет. Но вы можете использовать SomeTemplateClass<Animal*> и передавать на него указатели объектов производных классов.

Например, если у вас есть шаблонный класс Stack, где вы хотите разместить всевозможных животных. Вы можете просто сделать следующее:

Stack<Animal*> s; 
Dog d; 
s.push(&d); 
Cat c; 
s.push(&c)
3 голосов
/ 20 марта 2011

Нет, вам придется использовать указатели, например Stack<Animal*> (или какой-нибудь умный указатель).Причина в том, что Dog, Cat, Horse и т. Д. Не обязательно имеют одинаковый размер, поскольку они могут добавлять переменные-члены.

Контейнер может выделять пространство, которое достаточно велико для храненияAnimal.Если Dog больше этого, контейнер попытается скопировать-создать Dog, который помещается в него в слишком маленьком пространстве, что может привести к повреждению памяти.

0 голосов
/ 20 марта 2011

Это зависит от того, как шаблон использует передаваемый тип. Если вы имеете в виду стандартные контейнеры (например, std::vector, std::map и т. Д.), То ответ отрицательный. Между std::vector<Animal> и std::vector<Dog> вообще нет никакой связи, даже если в вашей классовой иерархии собаки происходят от животных.

Вы не можете поместить Dog в std::vector<Animal> ... C ++ использует семантику копирования, и вы будете подвергаться так называемому " нарезке ", что означает, что ваш экземпляр Dog потеряет любой член, которого также нет в базовом Animal классе.

Однако в целом, конечно, шаблон может использовать тип по-разному, что позволит, следовательно, принимать экземпляры производных классов. Например, в следующем коде шаблон MethodCaller может быть создан с типом, но с использованием экземпляра производного типа и правильной обработкой поздней отправки связывания. Это возможно, потому что экземпляр MethodCaller содержит только ссылку и не создает копию объекта.

#include <stdio.h>

template<typename T>
struct MethodCaller
{
    T& t;
    void (T::*method)();
    MethodCaller(T& t, void (T::*method)())
        : t(t), method(method)
    {}
    void operator()() { (t.*method)(); }
};

struct Animal { virtual void talk() = 0; };
struct Dog : Animal { virtual void talk() { printf("Bark\n"); } };
struct Cat : Animal { virtual void talk() { printf("Meow\n"); } };
struct Crocodile : Animal { virtual void talk() { printf("??\n"); } };

void makenoise(Animal *a)
{
    MethodCaller<Animal> noise(*a, &Animal::talk);
    noise(); noise(); noise();
}

int main()
{
    Dog doggie;
    Cat kitten;
    Crocodile cocco;
    makenoise(&doggie);
    makenoise(&kitten);
    makenoise(&cocco);
}

Также возможно реализовать класс Stack, как вы хотите ...

#include <vector>

template<typename T>
struct Stack
{
    std::vector<T *> content;
    ~Stack()
    {
        for (int i=0,n=content.size(); i<n; i++)
            delete content[i];
    }

    template<class S>
    void push(const S& s)
    {
        content.push_back(new S(s));
    }

    template<class S>
    S pop()
    {
        S result(dynamic_cast<S&>(*content.back()));
        content.pop_back();
        return result;
    }

private:
    // Taboo
    Stack(const Stack&);
    Stack& operator=(const Stack&);
};

int main()
{
    Dog doggie;
    Cat kitten;
    Crocodile cocco;

    Stack<Animal> s;
    s.push(doggie);
    s.push(kitten);
    s.push(cocco);

    Crocodile cocco2 = s.pop<Crocodile>();
    Cat kitten2 = s.pop<Cat>();
    Dog doggie2 = s.pop<Dog>();
}

Обратите внимание, что в реализации я использовал std::vector для хранения указателей на животных и, следовательно, во избежание проблем с нарезкой. Я использовал шаблонный метод, чтобы иметь возможность принимать производные типы в вызове push.

Также обратите внимание, что при совании животных вы должны указать класс, и если он неправильный (например, вы выдаваете Crocodile, когда верхний элемент в стеке равен Dog), вы получите bad_cast исключение во время выполнения.

0 голосов
/ 20 марта 2011

NO Stack<Animal> и Stack<Dog> - это совершенно разные классы.

Вы не можете даже разыграть между Stack<Animal> и Stack<const Animal>.

Редактировать: Но, как указал @Mihran, вы можете попробовать использовать Stack<Animal* > вместо Stack<Animal>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...