#include <iostream>
struct X
{
virtual void x() = 0;
};
struct Y
{
virtual void y() = 0;
};
struct XY : X, Y
{
void x() override { std::cout << "Xn"; }
void y() override { std::cout << "Yn"; }
};
int main()
{
XY xy;
X* xptr = &xy;
Y* yptr = (Y*)xptr;
yptr->y(); //prints "X"....
((Y*)((X*)(&xy)))->y(); // prints "Y"....
}
输出:
X
Y
有人能详细解释一下为什么会发生这种情况吗?为什么第一个调用打印X
,以及为什么两个调用彼此不同?
正如评论中所提到的,就语言而言,这是未定义的行为。
然而,实际选择的行为确实揭示了典型C++编译器的内部是如何工作的,因此研究为什么会得到这样的输出仍然很有趣。话虽如此,但重要的是要记住,以下解释并不普遍。没有硬性要求以这种方式工作,任何依赖于这种行为的代码都会被有效地破坏,即使它在您尝试的所有编译器上都有效。
C++多态性通常使用vtable来实现,vtable基本上是函数指针的列表,可以被视为对象中的隐藏成员指针。
所以
struct X
{
virtual void x() = 0;
};
struct Y {
virtual void y() = 0;
};
大致相当于(它实际上没有使用std::function<>
,但这使伪代码更清晰(:
struct X {
struct vtable_t {
std::function<void(void*)> first_virtual_function;
};
vtable_t* vtable;
void x() {
vtable->first_virtual_function(this);
}
};
struct Y {
struct vtable_t {
std::function<void(void*)> first_virtual_function;
};
vtable_t* vtable;
void y() {
vtable->first_virtual_function(this);
}
};
请注意X::vtable_t
和Y::vtable_t
是如何不约而同地本质上是相同的。如果X
和Y
具有不同的虚拟功能,事情就不会整齐地排列在一起。
另一个重要的难题是多重继承实际上是一种串联:
struct XY : X, Y {
void x() override { std::cout << "Xn"; }
void y() override { std::cout << "Yn"; }
};
// is roughly equivalent to:
struct XY {
static X::vtable vtable_for_x; // with first_virtual_function assigned to XY::x()
static Y::vtable vtable_for_y; // with first_virtual_function assigned to XY::y()
X x_base;
Y y_base;
XY() {
x_base.v_table = &vtable_for_x;
y_base.v_table = &vtable_for_y;
}
void x() { std::cout << "Xn"; }
void y() { std::cout << "Yn"; }
};
这意味着从多重继承类型转换为基类型不仅仅是更改指针类型的问题,值也必须更改。
只有X
指针等效于基对象指针,Y
指针实际上是不同的地址。
X* xptr = &xy;
// is equivalent to
X* xptr = &xy->x_base;
Y* xptr = &xy;
// is equivalent to
Y* xptr = &xy->y_base;
最后,当您从X
强制转换为Y
时,由于这些类型不相关,因此操作是reinterpret_cast
,因此虽然指针可能是指向Y
的指针,但底层对象仍然是X
。
幸运的是,事情进展顺利:
- X和Y都将vtable指针作为第一个成员对象
- X和Y的vtable实际上是等价的,前者指向
XY::x()
,后者指向XY::y()
因此,当调用y()
的逻辑应用于类型为X
的对象时,比特恰好排队调用XY::x()
。
Y* yptr = (Y*)xptr;
执行reinterpret_cast
来自显式类型转换( new_type ) expression
:
当遇到C样式的强制转换表达式时,编译器会尝试按以下顺序将其解释为强制转换表达式:
a(
const_cast<new_type>(expression);
b(具有扩展名的static_cast<new_type>(expression)
:派生类的指针或引用还允许强制转换为指向明确基类的指针或参考(反之亦然(,即使基类不可访问(也就是说,此强制转换忽略私有继承说明符(。同样适用于将指向成员的指针强制转换为指向明确非虚拟基的成员的指针
c(static_cast
(带扩展(然后是const_cast;
d(reinterpret_cast<new_type>(expression);
e(reinterpret_cast
然后是const_cast
。即使无法编译,也会选择满足相应铸造操作员要求的第一个选项
a、b和c将不起作用,因此它将降落在d上。在执行C样式强制转换时,甚至不会考虑正确的强制转换dynamic_cast
,因此在执行yptr->y()
时,您仍然有一个指向XY
的X
部分的指针,您可以通过Y
的眼睛来取消引用该部分。这使得您的程序具有未定义的行为。
切勿使用C型铸造。最好是明确一点,这样你就知道你得到了正确的演员阵容:
Y* yptr = dynamic_cast<Y*>(xptr);