Reputation: 163
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..).
When 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);
Upvotes: 5
Views: 292
Reputation: 114461
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.
Upvotes: 0
Reputation: 363507
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.
Upvotes: 3
Reputation: 11088
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)
Upvotes: 5
Reputation: 5693
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>
Upvotes: 0