我刚刚在使用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 }
这段代码是我刚修复的bug的简化版本,基本上我尝试重新生成bug,这样如果我不用dynamic_cast替换reinterpret_cast,就会在第22行返回一个狂野的枚举号码;在我将其更改为dynamic_cast后,将返回正确的枚举。
但是上面的代码实际上运行良好,它会输出“1”,即enum TWO。
也许我的简化有一些问题,但你认为使用reinterpret_case上述代码有可能出现问题吗?
是的,我知道使用reinterpret_case是没有意义的,只是我想知道发生了什么。
答案 0 :(得分:1)
您看到了这一点,因为B
中包含的D
不存在于同一地址。这可能与虚拟方法调度表的实现有关。该语言没有这样的保证,因为B
和D
都不是POD类型。
reinterpret_cast
基本上是你告诉编译器“采用这个值的位模式并将其视为其他类型,而不更改它。”
你触及了我正在寻找的东西,关于调度表,但它不是那么清楚,你能详细说明吗?
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;
};
此外,编译器自动生成一些代码和您的虚拟方法(即使您以这种方式编写它也可能无法内联,因为需要指向它的指针才能使虚拟表工作): / p>
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
将会像您预期的那样失败。
这实际上是实现定义的行为。
答案 1 :(得分:0)
reinterpret_cast
无法正确处理类型调度。如果您已经知道要使用的类型,请使用static_cast
。如果您不知道要使用的类型,请使用dynamic_cast
并确保dynamic_cast
返回的指针有效。
答案 2 :(得分:0)
在单继承层次结构中,当层次结构的最顶级类已经是多态的时,层次结构中的所有转换都执行相同的操作:无。它们只是“概念上”将指针值重新解释为不同类型的值。当用于向下转发时,只有dynamic_cast
会执行一些额外的检查。
出于这个原因,reinterpret_cast
恰好为此工作“工作”。鉴于你的课程定义,没有办法强迫它不起作用。