你能解释一下这个有缺陷的例子中发生了什么:
Base base; Derived* d = reinterpret_cast<Derived*> (&base);
d->method();
d->virtual_method();
//output: Derived-method() Base-virtual_method()
Run Code Online (Sandbox Code Playgroud)
我希望这段代码能够反过来行事.可能编译器为Base和Derived共享一个内存布局,当然vtable很常见.
所以我期待看到:
//output: Base-method() Derived-virtual_method()
Run Code Online (Sandbox Code Playgroud)
base是一个Base对象; 您将其字节重新解释为Derived对象,然后尝试将其用作Derived对象.执行此操作时的行为未定义.你的程序可能崩溃; 看起来似乎做对了; 它可能会使你的电脑点燃.
请注意,使用它reinterpret_cast来强制转换类层次结构永远不正确.您必须使用static_cast或dynamic_cast(或者,如果您要转换为基类,则不需要演员表).
为了解释为什么你会看到这种特殊行为,当你调用非虚拟成员函数时(d->method()假设method是非虚函数成员函数Derived),被调用的函数是在编译时确定的,而不是在运行时.
在这里,编译器知道d指向一个D对象(因为你已经欺骗了编译器,并说它是),所以它生成调用的代码Derived::method().根本没有"相对于指针的偏移".不需要进行任何计算,因为在编译程序时要知道要调用的函数.
只有在调用虚拟成员函数时才需要进行表查找(即使这样,只有在编译器不知道调用成员函数的对象的动态类型时才需要查找).
当你打电话时d->virtual_method(),Base::virtual_method会被叫.为什么?在C++的这个特定实现中,具有虚拟成员函数(多态类类型)的类类型的对象的前几个字节包含标识实际的标记(称为"vptr"或"虚拟表指针").对象的类型.当您调用虚拟成员函数时,则在运行时检查标记并根据该标记选择调用的函数.
当您重新解释base为Derived对象时,您实际上并未更改对象本身,因此其标记仍然表明它是一个Base对象,因此Base::virtual_method调用它的原因.
但请记住,所有这些恰好恰好是使用特定版本的特定编译器编译此代码时发生的情况.行为未定义,这只是未定义行为可以表现出来的一种方式.