XY 继承自 X 和 Y.将 XY* 转换为 X*,然后转换为 Y*,然后调用 Y 的函数会导致调用 X 的函数
XY inherits from both X and Y. casting XY* to X* then to Y* then calling Y's function results in calling X's function
#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);
- 等待整个 omp 块完成,然后再调用第二个函数
- 如何为 std::vector 分配内存,然后稍后为某些元素调用构造函数?
- 我的 cout 上有一个奇怪的输出,它把答案放在第一位,然后在我调用它的地方放一个奇怪的输出.我该怎么办?
- 如何在C++中获取lua函数作为参数,然后调用它
- 钩/绕道 d3d9 (现在/结束场景) - 似乎调用我的函数然后崩溃
- C++当您取消引用指向类对象的指针,然后将其作为引用返回时,是否可以对此引用调用方法
- C++:将向量传递给函数,然后在main中调用函数.错过了什么
- 努力将指向成员函数的指针绑定到类模板的T成员,然后在槽中调用
- 缓存大量回调,然后批量调用它们,无需v表成本
- 为什么部分初始化一个类然后调用委托 ctor 失败?
- 将整数向量转换为字节数组向量,然后调用每个字节数组
- 存储在基本分配中,然后调用基本功能
- 将复制构造函数超载与DELETE,然后调用C 子类的默认构造函数
- 复制指针,然后调用 delete
- C++如何将数组传递给函数,然后调用它来显示数组的值
- munmap_chunk 当操作员调用时指针无效,然后调用 destroctor
- 用管理器注册一个类,然后调用子类被覆盖的方法
- 需要某种方法将函数存储在列表中,然后调用它们
- 如何从类型列表继承,然后调用继承成员列表中的成员
- 创建结构实例数组,然后调用其他结构内部的函数,传递对象数组(指针)