C ++标准说" ...对虚函数调用的解释取决于调用它的对象的类型(动态类型)" (第252页)然后
"如果指针p
的静态类型是指向B
'的指针指向类D
的对象,派生自B
,动态类型*p
为D
" (第2页)。这里B
是基类,D
是派生类。
这似乎暗示(对我而言)如果我说
D d;
B *p = new B();
*p = d;
然后,如果f()
中的B
是虚拟的,p->f()
应该调用D::f()
,这是错误的。我想我不清楚"指向一个类的对象......"的含义。我知道,如果我说p = &d
,那么会调用D::f()
,但我想知道为什么上面的错误。
答案 0 :(得分:4)
D d;
B *p = new B();
*p = d;
最后一行将d
分配给*p
。这意味着它将使用D
赋值运算符复制B
实例,对象将为sliced。在这种情况下,*p
的动态类型仍为 B
。
p = &d;
将指向D
对象的指针指定给p
。在这种情况下不会发生切片,因为您只是分配指针,而不是对象本身。在这种情况下,*p
的动态类型为D
。
答案 1 :(得分:2)
答案的关键在于,在C ++中,对象的动态类型永远不会更改。您正在考虑将赋值表达式*p = d
视为以*p
完全替换对象d
的对象。但事实并非如此。在C ++中,对象永远不能真正替换另一个对象。
由于涉及类类型,*p = d
只使用参数B
调用类*p
的赋值运算符(D
的静态类型) 。 C ++中的对象只能被操作,它不能真正被替换。"
当然,我们谈论复制对象,分配它们等等。但这只是为了方便,因为大多数时候,非常精确的语义并不重要和考虑=
将一个对象分配给另一个对象很简单。但在内心深处,它既可以对目标对象进行函数调用(对于类类型),也可以将某些位的值复制到目标对象的空间(对于基本类型)。目标对象本身始终存在。
答案 2 :(得分:1)
正如 TartanLlama 所述,在第三行中,您错误地""错误地"将d分配给P指向的对象,从而我们称之为切片。来自 Professional C ++ ,
...在向上转换时,使用指针或超类的引用来避免切片
D d;
B *p = new B();
*p = d;
这意味着正确的代码会根据下面的代码进行评估
D d;
B *p = new B();
p = &d;
OR
D d;
B *p = new B();
p = static_cast<B*>(&d);
因此, p (* p)指向的对象的动态类型仍然是 D 。这允许您通过向上转换或向下转换指向子类的指针而不是对象来来回切换继承层次结构。