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

标签 c++ inheritance pointers

对于这种基本问题herehere,我很少看到“宠物”和“狗”类型的示例,但是它们对我来说没有意义,这就是原因。

假设我们具有以下类结构

class Pet {};
class Dog : public Pet {};

然后下面的语句

a (Dog) is a (Pet)



在我看来,在现实生活中可能是正确的,但在C++中不是正确的。只需看一下Dog对象的逻辑表示,它看起来像这样:

说的比较合适

a (Dog) has a (Pet)



或者

a (Pet) is a subset of (Dog)



如果您注意到这与“狗是宠物”在逻辑上相反

现在的问题是下面的#1被允许,而#2则不允许:
Pet* p = new Dog;  // [1] - allowed!
Dog* d = new Pet;  // [2] - not allowed without explicit casting!

我的理解是[1]在没有警告的情况下是不允许的,因为仅仅因为Pet对新成员一无所知,指针就不可能指向其超集类型的对象(Dog对象是Pet的超集)。那只狗可能已经声明过(上图中的“狗-宠物”子集)。
[1]等效于试图指向int*对象的double!

很显然,我在这里缺少一个要点,这会使我的整个推理变得颠倒了。你能告诉我这是什么吗?

我相信,与现实世界中的例子相提并论只会使事情复杂化。我希望从技术细节上理解这一点。谢谢!

最佳答案

编辑:重新阅读您的问题和我的答案使我在顶部说这一点:

您对C++中的 is a (通常是多态)的理解是错误的。

A is B 的意思是A has at least the properties of B, possibly more定义为

这与您的陈述一致,即Dog具有Pet,并且Pet的[属性]是Dog [属性]的子集。

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

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]部分,并可选地具有其他任何内容。从上面的 list 中,Pet *p可以指向这三个中的任何一个。只要您使用Pet *p来访问对象,就只能访问[pet data],因为您不知道其后是什么(如果有的话)。这是一个契约(Contract),上面写着这至少是一只宠物,也许更多

无论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!

My understanding is that 1 should not be allowed without warnings because there is no way a pointer should be able to point to an object of its superset's type (Dog object is a superset of Pet) simply because Pet does not know anything about the new members that Dog might have declared (the Dog - Pet subset in the diagram above).



指针p期望在它指向的位置找到[pet data]。由于右侧是Dog,并且每个Dog对象的[pet data]前面都有[dog data],因此完全可以指向Dog类型的对象。

编译器不知道指针后面是什么,否则,这就是为什么您无法通过[dog data]访问p的原因。

该声明是允许的,因为编译器可以在编译时保证[pet data]的存在。 (此语句显然已从实际简化,以适合您的问题描述)

1 is equivalent of an int* trying to point to a double object!



int和double之间没有这种子类型关系,C++
中的DogPet 之间也不存在这种子类型关系。尝试不要将它们混入讨论中,因为它们是不同的:您在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]

[3]如果p并不是真正的Dog,它将始终成功并可能导致难以跟踪的错误。
如果NULL并不是真正的p,则[4]将返回Dog

我热烈建议尝试这些转换,看看会得到什么。假设已启用RTTI,则应从[dog data]中获取static_cast的垃圾,并从NULL中获取dynamic_cast指针。

关于c++ - 为什么派生类指针在没有强制转换的情况下不能指向基类对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9752255/

相关文章:

c++ - 如何从继承的父类创建 unique_ptr

c - 无法在 C 中正确使用结构指针

c - 带有结构体指针数组的 strcpy() 的段错误

c++ - 变量的值为整数和 double

Python 类外键继承

c++ - 由于堆/堆栈损坏导致的严重段错误

c - C 中的半继承 : How does this snippet work?

c++ - 由于分配,c++中的指针算术

c++ - 断开连接后正确杀死 asio steady_timer

c++ - 使用 boost::asio::streambuf