我有以下类层次结构:
class IControl
{
virtual void SomeMethod() = 0; // Just to make IControl polymorphic.
};
class ControlBase
{
public:
virtual int GetType() = 0;
};
class ControlImpl : public ControlBase, public IControl
{
public:
virtual void SomeMethod() { }
virtual int GetType()
{
return 1;
}
};
我有一个 IControl 抽象类和一个 ControlBase 类。 ControlBase类不从IControl继承,但我知道每个IControl实现都将从ControlBase派生。
我有以下测试代码,我使用 dynamic_cast IControl - 参考投射到 ControlBase (因为我知道它来源于它) >,以及 C风格演员:
int main()
{
ControlImpl stb;
IControl& control = stb;
ControlBase& testCB1 = dynamic_cast<ControlBase&>(control);
ControlBase& testCB2 = (ControlBase&)control;
ControlBase* testCB3 = (ControlBase*)&control;
std::cout << &testCB1 << std::endl;
std::cout << &testCB2 << std::endl;
std::cout << testCB3 << std::endl;
std::cout << std::endl;
std::cout << testCB1.GetType() << std::endl; // This properly prints "1".
std::cout << testCB2.GetType() << std::endl; // This prints some random number.
std::cout << testCB3->GetType() << std::endl; // This prints some random number.
}
只有dynamic_cast正常工作,其他两个强制转换才会返回略有不同的内存地址,GetType()函数会返回不正确的值。
这是什么原因? C风格演员最终是否使用 reinterpret_cast ?它与多态对象在内存中的对齐方式有关吗?
答案 0 :(得分:8)
我认为你的例子中的类名有点令人困惑。我们称他们为Interface
,Base
和Impl
。请注意,Interface
和Base
无关。
C ++标准定义了C风格的强制转换,在[expr.cast]中称为“显式类型转换(强制转换符号)”。您可以(也许应该)阅读整个段落,以确切了解C样式转换的定义方式。对于OP中的示例,以下内容就足够了:
C风格可以执行[expr.cast] / 4之一的转换:
const_cast
static_cast
static_cast
后跟const_cast
reinterpret_cast
reinterpret_cast
后跟const_cast
此列表的顺序很重要,因为:
如果转换可以用上面列出的多种方式解释,则使用列表中首先出现的解释,即使由该解释产生的转换格式不正确。
让我们来看看你的例子
Impl impl;
Interface* pIntfc = &impl;
Base* pBase = (Base*)pIntfc;
无法使用const_cast
,列表中的下一个元素是static_cast
。但是,Interface
和Base
类无关,因此没有static_cast
可以从Interface*
转换为Base*
} 的。因此,使用了reinterpret_cast
。
附加说明: 您问题的实际答案是:由于上面的列表中没有dynamic_cast
,因此C风格的广告素材从不像dynamic_cast
。
实际地址更改的方式不是C ++语言定义的一部分,但我们可以举例说明如何实现:
具有至少一个虚函数(继承或拥有)的类的每个对象包含(读取:可以包含,在此示例中)指向vtable的指针。如果它从多个类继承虚函数,它包含多个指向vtable的指针。由于空基类优化(没有数据成员),Impl
的实例可能如下所示:
+=Impl=======================================+ | | | +-Base---------+ +-Interface---------+ | | | vtable_Base* | | vtable_Interface* | | | +--------------+ +-------------------+ | | | +============================================+
现在,例子:
Impl impl;
Impl* pImpl = &impl;
Interface* pIntfc = pImpl;
Base* pBase = pImpl;
+=Impl=======================================+ | | | +-Base---------+ +-Interface---------+ | | | vtable_Base* | | vtable_Interface* | | | +--------------+ +-------------------+ | | ^ ^ | +==|==================|======================+ ^ | | | +-- pBase +-- pIntfc | +-- pimpl
如果您改为reinterpret_cast
,结果是实现定义的,但可能会产生如下结果:
Impl impl;
Impl* pImpl = &impl;
Interface* pIntfc = pImpl;
Base* pBase = reinterpret_cast<Base*>(pIntfc);
+=Impl=======================================+ | | | +-Base---------+ +-Interface---------+ | | | vtable_Base* | | vtable_Interface* | | | +--------------+ +-------------------+ | | ^ | +=====================|======================+ ^ | | +-- pIntfc | | +-- pimpl +-- pBase
即。地址未更改,pBase
指向Interface
对象的Impl
子对象。
请注意,取消引用指针pBase
已经将我们带到UB-land,标准没有指定应该发生什么。在此示例性实现中,如果您调用pBase->GetType()
,则使用vtable_Interface*
,其中包含SomeMethod
条目,并调用该函数。这个函数不会返回任何内容,所以在这个例子中,召唤鼻子恶魔并接管世界。或者从堆栈中取一些值作为返回值。
答案 1 :(得分:3)
这是什么原因?
确切的原因是dynamic_cast
保证在这种情况下通过标准工作,而其他类型调用未定义的行为。
C风格演员最终是否使用了reinterpret_cast?
是的,在这种情况下确实如此。 (旁注:永远不会使用C风格的演员表。)
它与多态对象在内存中的对齐方式有关吗?
我想说它与使用多重继承的多态对象在内存中的布局方式有关。在具有单一继承的语言中,dynamic_cast
不是必需的,因为基础子对象地址将与派生对象地址重合。在多继承的情况下,情况并非如此,因为存在多个基础子对象,并且不同的基础子对象必须具有不同的地址。
有时,编译器可以计算每个子对象地址与派生对象地址之间的偏移量。如果偏移量不为零,则转换操作变为指针加法或减法而不是无操作。 (在虚拟继承 upcast 的情况下,它有点复杂,但编译器仍然可以这样做。)
至少有两种情况,编译器无法做到这一点:
在这些情况下,dynamic_cast
是唯一的投射方式。