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.