Let me expand a bit on my previous answer[^]. As I said, you can convert a pointer (or reference) to pointer (or reference) to the base class. This called "upcasting" because you are moving "up" in the inheritance tree. The conversion is "non-destructive", in other words you can later on "downcast" (move from base to derived) as long as you know what type of object was in the first place. An example:
struct Unit {
Unit (int tag) : dog_tag(tag){};
virtual std::string kind () = 0;
int dog_tag;
};
struct Soldier : public Unit {
Soldier (int tag) : Unit(tag) {};
virtual std::string kind() {return "soldier";};
float running_speed;
}
struct Sailor : public Unit {
Sailor (int tag) : Unit(tag) {};
virtual std::string kind() {return "sailor";}
int life_jackets;
};
std::vector actors {new Soldier(555), new Sailor(666);};
int main () {
Unit* u0 = actors[0];
std::cout << "Actor 0 is a " << u0.kind() " with tag " << u0.dog_tag << std::endl;
If you would look with a debugger at u0 you would see a memory layout something like this:
u0
0x1234
------->
pointer to vtable of Soldier
dog_tag
-----------------------------
running_speed
------------------------------
You can see now why the code works: no matter what kind of object you deal with, the first part of the memory layout is the same. Compiler simply ignores whatever is after the dog_tag field. A few more lines of code (please ignore that I didn't initialize other fields):
Soldier *s = (Soldier *)u0;
std::cout << "Running speed " << s->running_speed << std::endl;
s has exactly the same value as u0 but compiler considers it a pointer to a Soldier object so it uses the value from the running_speed field. I could have written:
Sailor *ss = (Sailor*)u0;
std::cout << "Sailor has " << ss->life_jackets << " life jackets";
And compiler wouldn't have cared at all. It would have accessed the memory at lif