对于这种基本问题here和here,我很少看到“宠物”和“狗”类型的示例,但是它们对我来说没有意义,这就是原因。
假设我们具有以下类结构
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
[属性]的子集。
这是多态性和继承的定义问题。您绘制的图与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]
部分,并可选地具有其他任何内容。从上面的 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++ 中的
Dog
和Pet
之间也不存在这种子类型关系。尝试不要将它们混入讨论中,因为它们是不同的:您在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
,它将始终成功并可能导致难以跟踪的错误。如果
NULL
并不是真正的p
,则[4]将返回Dog
。我热烈建议尝试这些转换,看看会得到什么。假设已启用RTTI,则应从
[dog data]
中获取static_cast
的垃圾,并从NULL
中获取dynamic_cast
指针。
关于c++ - 为什么派生类指针在没有强制转换的情况下不能指向基类对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9752255/