Редактировать: перечитывание вашего вопроса и мой ответ заставляют меня сказать это вверху:
Ваше понимание is a
в C ++ (полиморфизм в целом) неверно.
A is B
означает A has at least the properties of B, possibly more
, по определению .
Это совместимо с вашими утверждениями о том, что Dog
имеет Pet
и что [атрибуты] домашнего питомца [являются] подмножеством [атрибутов] Dog
.
Это вопрос определения полиморфизма и наследования. Рисованные вами диаграммы соответствуют представлению в памяти экземпляров Pet
и Dog
, но вводят в заблуждение при их интерпретации.
Pet* p = new Dog;
Указатель p
определен так, чтобы указывать на любой Pet-совместимый объект , который в C ++ является любым подтипом Pet
(Примечание: Pet
сам по себе является подтипом по определению) , Среда выполнения гарантируется, что при доступе к объекту за p
он будет содержать все, что ожидается от Pet
, и, возможно, больше . Часть «возможно больше» - это Dog
на вашей диаграмме. То, как вы рисуете свою диаграмму, вводит в заблуждение.
Подумайте о расположении членов класса в памяти:
Pet: [pet data]
Dog: [pet data][dog data]
Cat: [pet data][cat data]
Теперь, когда Pet *p
указывает на, требуется наличие части [pet data]
и, при необходимости, чего-либо еще. Из приведенного выше списка Pet *p
может указывать на любой из трех. Пока вы используете Pet *p
для доступа к объектам, вы можете получить доступ только к [pet data]
, потому что вы не знаете, что, во всяком случае, потом . Это контракт, который гласит Это, по крайней мере, домашнее животное, может быть, больше .
На все, на что указывает Dog *d
, должны быть [pet data]
и [dog data]
. Таким образом, единственный объект в памяти, на который он может указывать, это собака. И наоборот, через Dog *d
вы можете получить доступ как к [pet data]
, так и к [dog data]
. Аналогично для Cat
.
Давайте интерпретируем заявления, которые вас смущают:
Pet* p = new Dog; // [1] - allowed!
Dog* d = new Pet; // [2] - not allowed without explicit casting!
Насколько я понимаю, 1 нельзя допускать без предупреждений
потому что никоим образом указатель не должен быть в состоянии указать на объект
типа своего надмножества (объект Dog является надмножеством Pet) просто
потому что Pet не знает ничего о новых участниках, которые Dog
могли бы объявить (подмножество собак - питомцев на диаграмме выше).
Указатель p
ожидает найти [pet data]
в том месте, на которое он указывает. Так как правая сторона - это Dog
, и каждый Dog
объект имеет [pet data]
перед [dog data]
, указывать на объект типа Dog
вполне нормально.
Компилятор не знает, что за указателем стоит else , и поэтому вы не можете получить доступ от [dog data]
до p
.
Объявление позволено , поскольку компилятор может гарантировать наличие [pet data]
во время компиляции. (это утверждение явно упрощено от реальности, чтобы соответствовать вашему описанию проблемы)
1 эквивалентно тому, что int * пытается указать на двойной объект!
Между типами int и double нет такого подтипа, как между Dog
и Pet
в C ++ . Постарайтесь не смешивать их в обсуждении, потому что они разные: вы приводите между значениями от int и double ((int) double
явно, (double) int
неявно), вы не можете приводить между указателями им . Просто забудь это сравнение.
Что касается [2]: объявление гласит: «d
указывает на объект, который имеет [pet data]
и [dog data]
, возможно, больше». Но вы выделяете только [pet data]
, поэтому компилятор сообщает, что вы не можете этого сделать.
Фактически, компилятор не может гарантировать, что это нормально, и он отказывается от компиляции. Существуют допустимые ситуации, когда компилятор отказывается компилировать, но вы, программист, знаете лучше. Вот для чего static_cast
и dynamic_cast
. Простейший пример в нашем контексте:
d = p; // won't compile
d = static_cast<Dog *>(p); // [3]
d = dynamic_cast<Dog *>(p); // [4]
[3] всегда будет успешным и приведет к ошибкам, которые трудно отследить, если p
на самом деле не Dog
.
[4] вернет NULL
, если p
на самом деле не Dog
.
Я настоятельно рекомендую попробовать эти броски, чтобы увидеть, что вы получите. Вы должны получить мусор для [dog data]
из static_cast
и указатель NULL
для dynamic_cast
, предполагая, что RTTI включен.