为什么派生类指针不能在没有强制转换的情况下指向基类对象?

Laz*_*zer 7 c++ inheritance pointers

在这里这里看到了很少有关于这类基本问题的宠物和狗类型的例子,但它们对我来说没有意义,这就是为什么.

假设我们有以下类结构

class Pet {};
class Dog : public Pet {};
Run Code Online (Sandbox Code Playgroud)

然后是以下声明

a (Dog) is a (Pet)

在我的观点中,在现实生活中可能是真的,但在C++中却不是这样.只需看看Dog对象的逻辑表示,它看起来像这样:

在此输入图像描述

更合适的说法

a (Dog) has a (Pet)

要么

a (Pet) is a subset of (Dog)

如果你注意到它与"狗是宠物"的逻辑相反


现在的问题是,#2不允许下面的#1:

Pet* p = new Dog;  // [1] - allowed!
Dog* d = new Pet;  // [2] - not allowed without explicit casting!
Run Code Online (Sandbox Code Playgroud)

我的理解是不[1]应该在没有警告的情况下被允许,因为指针不应该能够指向其超集类型的对象(Dog对象是Pet的超集),因为Pet对新成员一无所知狗可能已经宣布(上图中的狗 - 宠物子集).

[1]相当于int*试图指向一个double对象!

很明显,我错过了一个关键点,这将使我的整个推理颠倒过来.你能告诉我它是什么吗?

我相信与现实世界的例子相似只会使事情复杂化.我希望从技术细节方面理解这一点.谢谢!

Irf*_*rfy 30

编辑:重新阅读您的问题,我的回答让我在顶部说出这个:

您对is aC++(多态,一般)的理解是错误的.

A is B意思是A has at least the properties of B, possibly more,根据定义.

这与你的Doga Pet和[的属性]是[属性]的子集的陈述兼容Dog.


这是多态性和继承的定义问题.您绘制的图表与实例的内存中表示对齐PetDog,但在您解释它们的方式是误导.

Pet* p = new Dog;
Run Code Online (Sandbox Code Playgroud)

指针p被定义为指向任何与Pet兼容的对象,在C++中,它是任何子类型Pet(注意:Pet根据定义,它是自身的子类型).确保运行时,当p访问后面的对象时,它将包含Pet预期包含的任何内容,甚至可能包含更多内容."可能更多"部分是Dog您的图表中的部分.绘制图表的方式会带来误导性的解释.

想想内存中特定于类的成员的布局:

Pet: [pet data]
Dog: [pet data][dog data]
Cat: [pet data][cat data]
Run Code Online (Sandbox Code Playgroud)

现在,无论何时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!
Run Code Online (Sandbox Code Playgroud)

我的理解是不应该在没有警告的情况下允许1,因为指针不能指向其超集类型的对象(Dog对象是Pet的超集),因为Pet对新成员一无所知狗可能已经宣布(上图中的狗 - 宠物子集).

指针p期望[pet data]在它指向的位置找到.由于右手边是a Dog,并且每个Dog物体都[pet data]在它的前面,所以[dog data]指向一个类型的物体Dog是完全可以的.

编译器不知道什么别的是指针的后面,这就是为什么你不能访问[dog data]通过p.

允许声明是因为[pet data]编译器在编译时可以保证存在.(这个陈述显然是从现实中简化,以适合您的问题描述)

1相当于一个int*试图指向一个双重对象!

int和double之间没有这样的子类型关系,就像C++之间DogPet C++一样.尽量不要将这些混合到讨论中,因为它们是不同的:你在int和double的之间转换((int) double是显式的,(double) int是隐式的),你不能在指向它们的指针之间进行转换.忘了这个比较.

至于[2]:在声明指出" d指向具有一个对象[pet data] [dog data],可能更多." 但是你只是分配[pet data],所以编译器告诉你不能这样做.

实际上,编译器无法保证这是否正常并且拒绝编译.有合理的情况,编译器拒绝编译,但程序员,你知道更好.这就是static_castdynamic_cast是.我们上下文中最简单的例子是:

d = p; // won't compile
d = static_cast<Dog *>(p); // [3]
d = dynamic_cast<Dog *>(p); // [4]
Run Code Online (Sandbox Code Playgroud)

[3]将永远成功,并导致可能难以跟踪的错误,如果p不是真的Dog.如果不是真的,
[4]将会返回.NULLpDog

我热烈建议尝试这些演员,看看你得到了什么.假设RTTI已启用,您应该[dog data]static_castNULL指针获取垃圾.dynamic_cast


ami*_*mit 5

在技​​术细节方面:

Dog对象的附加信息被附加到对象的末尾Pet,因此前缀[以位为单位] Dog实际上是a Pet,因此将Dog*对象分配给Pet*变量没有问题.访问对象的任何Pet字段/方法是完全安全的Dog.

然而 - 对立不是真的.如果你的地址分配Pet*给一个Dog*变量,然后访问的领域之一Dog[这是不是在Pet]你会得到超出分配的空间.

键入推理:
另请注意,只有在没有强制转换的情况下才能将值赋给变量[c ++是静态类型语言].因为Dog是a Pet,Dog*Pet*- 所以这不是冲突的,但反过来却不是真的.