Y
对象进行切片,并将该对象的一部分复制到X
对象中。然后在X
对象上调用函数,因此调用了X
的函数。X
类型(也就是说,如果你希望表达式的静态类型为X
,但仍希望它表示一个Y
对象),那么你需要将其强制转换为引用类型。((X&)*y).foo()
这将调用Y
对象中的函数,不会切片或复制到一个X
对象中。具体而言:
y
,它是类型为Y*
的指针。解引用将得到一个lvalue表达式,类型为Y
。即使其静态类型为其基类类型,lvalue表达式实际上可以表示派生类型的对象。X&
类型转换,它是X
的引用。这将产生一个lvalue表达式,类型为X
。您原来的强制类型转换执行了以下操作:
y
。X
。这将导致将数据复制到一个新的X
对象中。该表达式的结果是一个静态类型为X
的rvalue表达式。所指对象的动态类型也是X
,这与所有rvalue表达式相同。强制类型转换总是创建一个新的对象,该对象的类型是您要进行转换的类型,并且使用您要进行转换的对象进行构造。
将其转换为X*会创建一个新的指针(即,类型为X*的对象)。它具有与y
相同的值,因此仍然指向类型为Y的同一对象。
将其转换为X会创建一个新的X。它使用*y
进行构造,但除此之外与旧对象无关。在您的示例中,foo()
在这个新的“临时”对象上调用,而不是在y
指向的对象上调用。
您正确地指出动态多态性仅适用于指针和引用,而不适用于对象,原因是:如果您有一个指向X的指针,则它指向的东西可能是X的子类。但是,如果您有一个X,则它是一个X,没有别的了。虚拟调用毫无意义。
(*)除非优化允许省略不改变结果的代码。但是,优化不允许更改调用foo()
函数的内容。
*y
),是没有问题的,但强制类型转换((X)
)创建了一个新的(临时)对象,特别是属于类X
——这就是强制类型转换的含义。因此,该对象必须具有来自类X
的虚表——需要考虑到转换将删除子类中添加的任何实例成员(事实上,X
的复制构造函数怎么可能知道它们?),所以如果Y
的任何重写代码执行,那么就有可能导致灾难——因为他们确信this
指向Y
的实例,包括所有添加的成员等等...当这种情况是错误的!
当然,使用指针进行强制类型转换的版本完全不同——*X
与Y*
具有完全相同的位,因此仍然指向完全有效的Y
实例(当然,它指向y
)。
我相信这只是语言规范的方式所致。在可能的情况下,引用和指针使用后期绑定,而对象使用早期绑定。在每种情况下都采用后期绑定是可能的(我想象中),但这样做的编译器将不符合C++规范。
我认为 Darth Eru 的解释是正确的,这就是我认为 C++ 行为如此的原因:
代码 (X)*y 就像创建了一个类型为 X 的本地变量。编译器需要在堆栈上分配 sizeof(X) 的空间,并且会丢弃包含在类型为 Y 的对象中的任何额外数据,所以当调用 foo() 时,它必须执行 X 版本。编译器要让你调用 Y 版本的行为可能很困难。
代码 (X*)y 就像创建了一个指向对象的指针,编译器知道指向的对象是 X 或 X 的子类。运行时,当您解引用指针并使用 "->foo()" 调用 foo 时,对象的类别被确定并使用正确的函数。