为什么C风格的演员表现与dynamic_cast不同?

Mar*_*cze 0 c++ polymorphism inheritance casting

我有以下类层次结构:

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

我有一个IControl抽象类和一个ControlBase类.ControlBase类不从IControl继承,但我知道每个IControl实现都将从ControlBase派生.

我有以下测试代码,其中我使用dynamic_castC样式转换IControl -reference转换为ControlBase(因为我知道它是从它派生的):

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.
}
Run Code Online (Sandbox Code Playgroud)

只有dynamic_cast正常工作,其他两个强制转换才会返回略有不同的内存地址,GetType()函数会返回不正确的值.

这是什么原因?C风格的演员最终是否使用了reinterpret_cast?它与多态对象在内存中的对齐方式有关吗?

dyp*_*dyp 8

我认为你的例子中的类名有点令人困惑.让我们给他们打电话Interface,BaseImpl.请注意,InterfaceBase无关的.

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;
Run Code Online (Sandbox Code Playgroud)

A const_cast不能使用,列表中的下一个元素是a static_cast.但是类InterfaceBase不相关的,因此不存在static_cast可以从转换Interface*Base*.因此,使用a reinterpret_cast.

附加说明:您的问题的实际答案是:由于dynamic_cast上面的列表中没有,C风格的演员表演从不像adynamic_cast.


实际地址如何变化不是C++语言定义的一部分,但我们可以举例说明如何实现它:

具有至少一个虚函数(继承或拥有)的类的每个对象包含(读取:在该示例中可以包含)指向vtable的指针.如果它从多个类继承虚函数,它包含多个指向vtable的指针.由于空基类优化(没有数据成员),实例Impl可能如下所示:

+=Impl=======================================+
|                                            |
|  +-Base---------+   +-Interface---------+  |
|  | vtable_Base* |   | vtable_Interface* |  |
|  +--------------+   +-------------------+  |
|                                            |
+============================================+

现在,例子:

     Impl  impl;

     Impl* pImpl  = &impl;
Interface* pIntfc = pImpl;
     Base* pBase  = pImpl;
Run Code Online (Sandbox Code Playgroud)
+=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);
Run Code Online (Sandbox Code Playgroud)
+=Impl=======================================+
|                                            |
|  +-Base---------+   +-Interface---------+  |
|  | vtable_Base* |   | vtable_Interface* |  |
|  +--------------+   +-------------------+  |
|                     ^                      |
+=====================|======================+
^                     |
|                     +-- pIntfc
|                     |
+-- pimpl             +-- pBase

即地址未更改,pBase指向Interface对象的子Impl对象.

请注意,取消引用指针pBase已将我们带到UB-land,标准没有指定应该发生什么.在此示例性实现中,如果您调用pBase->GetType(),vtable_Interface*则使用包含SomeMethod该条目的函数,并调用该函数.这个函数不返回任何东西,所以在这个例子中,召唤鼻子恶魔并接管世界.或者从堆栈中取一些值作为返回值.