Edit: re-reading my question, and my answer makes me say this at the top:
Your understanding of is a
in C ++ (polymorphism in general) is wrong.
A is B
means A has at least the properties of B, possibly more
, by definition .
This is consistent with your statements that Dog
has Pet
and that [attributes] of Pet are [are] a subset of [attributes] of Dog
.
It is a matter of defining polymorphism and inheritance. The drawings you draw are aligned with the representation of the Pet
and Dog
instances in mind, but are misleading in how you interpret them.
Pet* p = new Dog;
Pointer p
defined as pointing to any Pet-compatible object, which in C ++ is any subtype of Pet
(Note: Pet
is a subtype of itself by definition). Runtime is guaranteed that when accessing an object beyond p
, it will contain everything that Pet
should contain, and possibly more . “Perhaps more” is Dog
in your diagram. The way you draw your chart gives a false interpretation.
Think about the layout of class members in memory:
Pet: [pet data] Dog: [pet data][dog data] Cat: [pet data][cat data]
Now, when Pet *p
points out, you need to have a [pet data]
and, optionally, something else. From the list above, Pet *p
can point to any of the three. As long as you use Pet *p
to access objects, you can only access [pet data]
, because you do not know what if anything, afterwards. It’s a contract that says It’s at least Pet, maybe more .
What Dog *d
indicates must have [pet data]
and [dog data]
. Thus, the only object in memory that it can reference is a dog. And vice versa, through Dog *d
you can access both [pet data]
and [dog data]
. Similarly for Cat
.
Let's interpret the declarations you confuse:
Pet* p = new Dog; // [1] - allowed! Dog* d = new Pet; // [2] - not allowed without explicit casting!
I understand that 1 should not be allowed without warning, because the pointer should not point to the object of its subset (the Dog object is a subset of Pet) simply because Pet does not know anything about the new members that Dog (the subset of Dog - Pet in the diagram above).
Pointer p
expects to find [pet data]
in the place where it points. Since the right side is Dog
, and each Dog
object has [pet data]
in front of its [dog data]
, pointing to an object of type Dog
, everything is fine.
The compiler does not know what is behind the else pointer, so you cannot access [dog data]
via p
.
Declaration is allowed because the presence of [pet data]
can be guaranteed by the compiler at compile time. (this statement is obviously simplified from reality to fit the description of your problem)
1 is equivalent to int * trying to point to a double object!
There is no such subtype relationship between int and double as between Dog
and Pet
in C ++ . Try not to mix them with discussion because they are different: you add values from int and double ( (int) double
Explicit, (double) int
implicit), you cannot use pointers between them . Just forget this comparison.
Regarding [2]: the statement says: " d
points to an object with [pet data]
and [dog data]
, possibly more." But you only [pet data]
, so the compiler tells you that you cannot do this.
In fact, the compiler cannot guarantee if everything is in order, and it refuses to compile. There are legitimate situations where the compiler refuses to compile, but you, the programmer, know better. What are static_cast
and dynamic_cast
? The simplest example in our context:
d = p;
[3] will always succeed and lead to errors with errors if p
is not really Dog
.
[4] will return NULL
if p
not really Dog
.
I heartily suggest trying these exiles to see what you get. You should get garbage for [dog data]
from the static_cast
pointer and NULL
for dynamic_cast
, assuming RTTI is enabled.