我刚刚在使用reinterpret_cast的代码中发现了一个错误,将其更改为dynamic_cast后,问题就消失了。然后,我尝试使用以下代码重现该行为:
1 #include <iostream>
2
3 using namespace std;
4
5 typedef enum {
6 ONE, TWO,
7 } Num;
8
9 class B {
10 public:
11 virtual Num f() const = 0;
12 };
13
14 class D: public B {
15 public:
16 virtual Num f() const { return TWO; }
17 };
18
19 int main()
20 {
21 B *b = new D();
22 cout << "f()=" << reinterpret_cast<D*>(b)->f() << endl << endl;
23
24 return 0;
25 }
这段代码是我刚刚修复的错误的简化版本,基本上我尝试重新生成错误,这样如果我不将 reinterpret_cast 替换为 dynamic_cast,则会在第 22 行返回一个野生枚举数;在我将其更改为 dynamic_cast 之后,返回了正确的枚举。
但上面的代码实际上运行良好,它确实打印出“1”,这是枚举二。
也许我的简化有一些问题,但您是否看到上述代码在使用 reinterpret_case 时可能有问题?
是的,我知道使用 reinterpret_case 没有意义,只是我想知道发生了什么。
最佳答案
你看到这个是因为 B
包含在 D
中不存在于同一地址。这可能与虚方法调度表的实现有关。该语言不提供此类保证,因为 B
也不D
是 POD 类型。
reinterpret_cast
基本上是告诉编译器“采用这个值的位模式并将其视为其他类型,而不更改它。”
You touched [on] something which is what I am looking for, about the dispatch table, but it is not so clear, can you elaborate?
C++ 不规定编译器和/或 ABI 实现的细节。因此不能保证 reinterpret_cast
和 dynamic_cast
会做同样的事情。您偶然发现了一种情况(如 AndreyT 所指出的那样,是单继承层次结构),它在您的编译器上确实如此。
当你声明一个类有virtual
成员,它不再是存储对象的内容与类声明中所写完全相同的 POD 类型,因为编译器在存储开始处添加了一个隐藏的“虚拟表指针”。该指针指向包含该类虚成员详细信息的每个类表,例如指向该类虚方法的函数指针。例如,您写道:
class B {
public:
virtual Num f() const = 0;
};
但是为 B
存储了什么?大概是这样的:
struct __VirtualsForB {
Num (*f)(const B* this);
};
struct B {
const __VirtualsForB* const __vtbl;
};
然后你写:
class D: public B {
public:
// BTW, don't need to say `virtual` here. Virtual-ness is inherited.
virtual Num f() const { return TWO; }
};
的存储方式如下:
struct __VirtualsForD {
__VirtualsForB __super;
};
// This exists since D is not abstract.
extern const __VirtualsForD __virtuals_for_D;
struct D {
const __VirtualsForD* const __vtbl;
};
另外,编译器自动生成一些代码和你的虚拟方法(即使你这样写它也可能无法内联,因为虚拟表需要一个指向它的指针才能工作):
Num __D__f(const B* __in_this)
{
const D* this = static_cast<const D*>(__in_this);
return TWO;
}
const __VirtualsForD __virtuals_for_D = { __D__f } ;
然后当你写:
B *b = new D();
那变成了这样的东西:
// Allocate a D.
D* _new_D = (D*)operator new(sizeof(D));
// Construct the D.
_new_D.__vtbl = __virtuals_for_D;
B *b = static_cast<B*>(_new_D);
并且由于这个实现的一些巧合事实:
- 虚拟表中的第一件事为
D
是B
的虚拟表. B
中的第一件事或D
是指向对象类的虚拟表的指针。
恰好 reinterpret_cast
和 dynamic_cast
做同样的事情(即什么都不做)和你的 cout << reinterpret_cast<D*>(b)->f()
成功。顺便说一句,它变成了这样的东西:
B* __temp = static_cast<B*>(reinterpret_cast<D*>(b));
Num __temp2 = (*__temp.__vtbl.f)(__temp);
std::ostream::operator<<(cout, __temp2);
// ...
如果这些条件中的任何一个不成立,就像多重继承或虚拟基类继承中经常出现的情况,那么 reinterpret_cast
会像您预期的那样失败。
这实际上是实现定义的行为。
关于c++ - reinterpret_cast 的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20795275/