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 << "X\n"; }
void y() override { std::cout << "Y\n"; }
};
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 实现,它基本上是一个函数指针列表,可以看作是对象中的隐藏成员指针。
所以
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 << "X\n"; }
void y() override { std::cout << "Y\n"; }
};
// 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 << "X\n"; }
void y() { std::cout << "Y\n"; }
};
这意味着从多重继承类型转换为基类不仅仅是更改指针类型的问题,值 也必须更改。
只有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
来自 Explicit type conversion ( new_type ) expression
:
When the C-style cast expression is encountered, the compiler attempts to interpret it as the following cast expressions, in this order:
a) const_cast<new_type>(expression);
b) static_cast<new_type>(expression)
, with extensions: pointer or reference to a derived class is additionally allowed to be cast to pointer or reference to unambiguous base class (and vice versa) even if the base class is inaccessible (that is, this cast ignores the private inheritance specifier). Same applies to casting pointer to member to pointer to member of unambiguous non-virtual base;
c) static_cast
(with extensions) followed by const_cast;
d) reinterpret_cast<new_type>(expression);
e) reinterpret_cast
followed by const_cast
.
The first choice that satisfies the requirements of the respective cast operator is selected, even if it cannot be compiled
a、b 和 c 将不起作用,因此它落在 d。
正确的转换,dynamic_cast
,甚至在你做 C 风格的转换时都没有被考虑,所以你仍然有一个指向 XY
的 X
部分的指针,你通过眼睛取消引用Y
当你做 yptr->y()
。这使您的程序具有 undefined behavior.
永远不要使用 C 风格的转换。最好是明确的,这样你就知道你得到了正确的转换:
Y* yptr = dynamic_cast<Y*>(xptr);
#include <iostream>
struct X
{
virtual void x() = 0;
};
struct Y
{
virtual void y() = 0;
};
struct XY : X, Y
{
void x() override { std::cout << "X\n"; }
void y() override { std::cout << "Y\n"; }
};
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 实现,它基本上是一个函数指针列表,可以看作是对象中的隐藏成员指针。
所以
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 << "X\n"; }
void y() override { std::cout << "Y\n"; }
};
// 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 << "X\n"; }
void y() { std::cout << "Y\n"; }
};
这意味着从多重继承类型转换为基类不仅仅是更改指针类型的问题,值 也必须更改。
只有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
来自 Explicit type conversion ( new_type ) expression
:
When the C-style cast expression is encountered, the compiler attempts to interpret it as the following cast expressions, in this order:
a)
const_cast<new_type>(expression);
b)static_cast<new_type>(expression)
, with extensions: pointer or reference to a derived class is additionally allowed to be cast to pointer or reference to unambiguous base class (and vice versa) even if the base class is inaccessible (that is, this cast ignores the private inheritance specifier). Same applies to casting pointer to member to pointer to member of unambiguous non-virtual base;
c)static_cast
(with extensions) followed byconst_cast;
d)reinterpret_cast<new_type>(expression);
e)reinterpret_cast
followed byconst_cast
.The first choice that satisfies the requirements of the respective cast operator is selected, even if it cannot be compiled
a、b 和 c 将不起作用,因此它落在 d。
正确的转换,dynamic_cast
,甚至在你做 C 风格的转换时都没有被考虑,所以你仍然有一个指向 XY
的 X
部分的指针,你通过眼睛取消引用Y
当你做 yptr->y()
。这使您的程序具有 undefined behavior.
永远不要使用 C 风格的转换。最好是明确的,这样你就知道你得到了正确的转换:
Y* yptr = dynamic_cast<Y*>(xptr);