0

我有以下类层次结构:

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以及C 风格的强制转换对ControlBase的IControl引用(因为我知道它派生自它):

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吗?它与多态对象在内存中的对齐方式有关吗?

4

2 回答 2

8

我认为您示例中的类名有点令人困惑。让我们称它们InterfaceBaseImpl。注意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;

Aconst_cast不能使用,列表中的下一个元素是 a static_cast。但是类InterfaceBase无关,因此没有可以static_cast转换Interface*为的Base*。因此,reinterpret_cast使用 a。

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


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

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

+=Impl=========================================+
| |
| +-基础---------+ +-接口---------+ |
| | vtable_Base* | | vtable_Interface* | |
| +--------------+ +---------+ |
| |
+==============================================+

现在,示例:

     Impl  impl;

     Impl* pImpl  = &impl;
Interface* pIntfc = pImpl;
     Base* pBase  = pImpl;
+=Impl=========================================+
| |
| +-基础---------+ +-接口---------+ |
| | vtable_Base* | | vtable_Interface* | |
| +--------------+ +---------+ |
| ^ ^ |
+==|===================|=======================+
^ | |
| +-- pBase +-- pIntfc
|
+-- 粉刺

如果您改为执行 a reinterpret_cast,则结果是实现定义的,但可能会导致如下结果:

     Impl  impl;

     Impl* pImpl  = &impl;
Interface* pIntfc = pImpl;
     Base* pBase  = reinterpret_cast<Base*>(pIntfc);
+=Impl=========================================+
| |
| +-基础---------+ +-接口---------+ |
| | vtable_Base* | | vtable_Interface* | |
| +--------------+ +---------+ |
| ^ |
+=====================|=======================+
^ |
| +-- pIntfc
| |
+-- pimpl +-- pBase

即地址不变,pBase指向Interface对象的子Impl对象。

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

于 2013-08-03T20:41:00.413 回答
3

这样做的确切原因是什么?

确切的原因是dynamic_cast标准保证在这种情况下工作,而其他类型则调用未定义的行为。

C 风格的演员表最终会使用 reinterpret_cast 吗?

是的,在这种情况下确实如此。(附注:永远不要使用 C 风格的演员表)。

它与多态对象在内存中的对齐方式有关吗?

我想说这与使用多重继承的多态对象在内存中的布局方式有关。在具有单一继承的语言中,dynamic_cast这不是必需的,因为基子对象地址与派生对象地址一致。在多重继承的情况下,情况并非如此,因为有多个基础子对象,并且不同的基础子对象必须具有不同的地址。

有时编译器可以计算每个子对象地址和派生对象地址之间的偏移量。如果偏移量不为零,则强制转换操作将变为指针加法或减法,而不是无操作。(在虚拟继承upcast的情况下,它有点复杂,但编译器仍然可以做到这一点)。

至少有两种情况编译器无法做到这一点:

  1. 交叉转换(即在两个都不是另一个基类的类之间)。
  2. 从虚拟基地垂头丧气。

在这些情况下dynamic_cast是唯一的施法方式。

于 2013-08-03T20:52:34.840 回答