开发者

Polymorphic classes in templates

Let's say we have a class hierarchy where we have a generic Animal class, which has several classes directly inherit from it (such as Dog, Cat, Horse, etc..).

开发者_StackOverflowWhen using templates on this inheritance hierarchy, is it legal to just use SomeTemplateClass<Animal> and then shove in Dogs and Cats and Horses into this templated object?

For example, assume we have a templated Stack class, where we want to host all sorts of animals. Can I simply state Stack<Animal> s; Dog d; s.push(d); Cat c; s.push(c);


Answer of your question if No. But you can use SomeTemplateClass<Animal*> and pass pointers of objects of derived classes to it.

For example, if you have a templated Stack class, where you want to host all sorts of animals. You can simply do following:

Stack<Animal*> s; 
Dog d; 
s.push(&d); 
Cat c; 
s.push(&c)


No, you'd have to use pointers, i.e. Stack<Animal*> (or some kind of smart pointer). The reason is that Dog, Cat, Horse etc. are not necessarily the same size, since they might add member variables.

The container may allocate space that is only large enough to store an Animal. If a Dog is larger than that, the container will try to copy-construct a Dog that is pushed into it in too small a space, potentially causing memory corruption.


NO Stack<Animal> and Stack<Dog> are completely different classes.

You can't even cast between Stack<Animal> and Stack<const Animal>.

Edit: But as @Mihran pointed you can try to use Stack<Animal* > instead Stack<Animal>


It depends on what use the template does with the passed type. If you mean standard containers (e.g. std::vector, std::map and so on) then the answer is no. There is no relation at all between std::vector<Animal> and std::vector<Dog> even if in your class hierarchy dogs derive from animals.

You cannot put a Dog in an std::vector<Animal>... C++ uses copy semantic and you would incur in what is called "slicing" that means that your Dog instance will lose any member that is not also present in the base Animal class.

However in general it's of course well possible for a template to use the type in different ways that will allow therefore accept instances of derived classes. For example in the following code the template MethodCaller can be instantiated with a type but using an instance of a derived type and correctly handling late binding dispatch. This is possible because the MethodCaller instance only holds a reference and doesn't make a copy of the object.

#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);
}

It is also possible to implement the Stack class as you want...

#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>();
}

Note that in the implementation I've used an std::vector for keeping pointers to animals and therefore to avoid the slicing problem. I've been using a template method to be able to accept derived types in the push call.

Also note that when popping animals you must provide what is the class and if it's a wrong one (e.g. you pop out a Crocodile when the top element on the stack is a Dog) you will get a bad_cast exception at runtime.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜