Polymorphic Storage
Didn't we say that a big reason for doing this was so we could have a
std::vector
of animals for our Zoo?We did.
Let's do it. MORE animals!
Open up the code from before:
- Open Code in OnlineGDB
- Click the
main
, and then replace the code in the body ofmain
with the following code:
button on the top left, scroll down to
- Click the
auto cowNames = {"Bessie", "Mabel", "Daisy"};
auto raptorNames = {"Peri", "Rex", "Talon"};
std::vector<Animal> zoo;
for (auto name : cowNames) {
Cow tempCow(name);
zoo.push_back(tempCow);
}
for (auto name : raptorNames) {
Raptor tempRaptor(name);
zoo.push_back(tempRaptor);
}
for (Animal& animal : zoo) {
engageWith(animal);
}
The code makes three Cow
s and three Raptor
s, then adds them to our zoo
vector, and then calls engageWith()
on each of them (which has them speak()
and feed()
).
You should have seen something like
Bessie makes nonspecific animal noises
Bessie eats
Mabel makes nonspecific animal noises
Mabel eats
Daisy makes nonspecific animal noises
Daisy eats
Peri makes nonspecific animal noises
Peri eats
Rex makes nonspecific animal noises
Rex eats
Talon makes nonspecific animal noises
Talon eats
Generic Animals
Hay! What did we do wrong? Hadn't we solved this already?
Oh, no, I think I know what happened—we made
Cows
andRaptors
and then turned them into these zombie-like generic animals.Exactly.
Our std::vector
only stores base-class Animal
objects. Every time we call push_back
with a Cow
or a Raptor
, we only copy the base Animal
part of the object into the vector.
Okay, but I'm not seeing the right way to store an
Animal
object where it could actually really be aCow
or aRaptor
.You can't.
What?
Woah, what?
Huh?
Yes, it's pretty much impossible. Because we don't know how many extra data members a derived class might add, we don't even know how much space would be needed per element of the array.
But there is a way… a slightly indirect way.
Meh. It's going to be pointers, isn't it?
Oh, no.
Storing Pointers to Objects
When we're using derived classes, we often end up needing to use pointers.
We can switch out the code above with this code that uses a vector of pointers to animals:
auto cowNames = {"Bessie", "Mabel", "Daisy"};
auto raptorNames = {"Peri", "Rex", "Talon"};
std::vector<Animal*> zoo;
for (auto name : cowNames) {
zoo.push_back(new Cow{name});
}
for (auto name : raptorNames) {
zoo.push_back(new Raptor{name});
}
for (Animal* animal : zoo) {
engageWith(*animal);
}
while (!zoo.empty()) {
delete zoo.back();
zoo.pop_back();
}
Now run this version.
You should have seen
Bessie says: Mooooo
Bessie eats
Mabel says: Mooooo
Mabel eats
Daisy says: Mooooo
Daisy eats
Peri says: Rawrrr
Peri eats
Rex says: Rawrrr
Rex eats
Talon says: Rawrrr
Talon eats
So our code works! We just need to use pointers.
I tried to escape templates and I ended up with pointers. Oh, joy.
It's not so bad really.
I never had these problems in Java.
Remember, Java is pretty much all pointers all the time. Every time you have an object in Java, it's really a pointer to that object. (They call it a “reference” to try to soften the blow.)
What I want to know is why we were ever able to make these bland generic
Animal
objects in the first place—we only wantCow
s andRaptors
.Okay, let's see if we can sort out that aspect of things…
(When logged in, completion status appears here.)