是否存在将实际Base向下转换为Derived的情况?

时间:2013-11-28 10:43:23

标签: c++ casting undefined-behavior

在一般情况下,从(动态)Base下降到其中一个派生类Derived

是(非常应得的)未定义行为

明显的UB

class Base
{
public:
    virtual void foo()
    { /* does something */ }

    int a;
}

class Derived : public Base
{
public:
    virtual void foo()
    { /* does something different */ }

    double b;
}

Base obj;
Derived derObj = *static_cast<Derived *>(&obj);  // <- here come the demons

在目前的编译器实现方法中,这里显然至少存在Vtable和b中包含垃圾值的值不一致的问题。因此,在这些条件下标准不能定义向下转换的行为是有道理的。

不那么明显的天真案例

然而,我很想知道在特定情况下是否对此规则做出了一些让步? 举个例子:

class Base
{
public:
    void foo()
    { /* does something */ }

    int a = 1;
    double b = 2.;
}

class DerivedForInt : public Base
{
    int getVal()
    { return a }
}

Base obj;
DerivedForInt derObj = *static_cast<DerivedForInt *>(&obj);  // <- still an UB ?

在这里,我们可以很容易地想象编译器做正确的事情。 但从标准的角度来看,它仍未定义吗?

编辑: static_cast 是一个随机选择,只是为了说明目的,如果与其他演员一起工作也很有趣!

3 个答案:

答案 0 :(得分:7)

好的,我可能会因为这个答案而变成碎片......

显然,正如其他答案所述,这是未定义的行为,如标准中所见。但是,如果您的Base类具有标准布局且您的DerivedForInt类未添加新数据成员,则它将具有相同(标准)布局。

在这些条件下,即使是UB,你的演员阵容也不会引起麻烦。根据其中一位消息来源,至少可以安全地做到

DerivedForInt *derived = reinterpret_cast<DerivedForInt*>(&base.a);

来源:

What are Aggregates and PODs and how/why are they special?

PODs and inheritance in C++11. Does the address of the struct == address of the first member?

从第二个链接:

  

这是定义,来自标准第9节[class]:

     
    

标准布局类是一个类:

         
        
  • 没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员,
  •     
  • 没有虚函数(10.3),没有虚基类(10.1),
  •     
  • 对所有非静态数据成员具有相同的访问控制(第11条),
  •     
  • 没有非标准布局基类
  •     
  • 在大多数派生类中没有非静态数据成员,并且最多只有一个具有非静态数据成员的基类,或者没有包含非静态数据成员的基类,并且
  •     
  • 没有与第一个非静态数据成员相同类型的基类。
  •     
  
     

然后保证你想要的属性(第9.2节[class.mem]):

     
    

指向标准布局结构对象的指针,使用reinterpret_cast进行适当转换,指向其初始成员(或者如果该成员是位字段,则指向它所在的单位),反之亦然。

  
     

这实际上比旧的要求更好,因为通过添加非平凡的构造函数和/或析构函数不会丢失reinterpret_cast的能力。

答案 1 :(得分:3)

n3376 5.2.9 / 11

类型为“指向cv1 B的指针”的prvalue,其中B是类类型,可以转换为类型为“指针”的prvalue 到cv2 D,“其中D是从B派生的类(第10章),如果从”指针D“的有效标准转换 “指向B的指针”存在(4.10),cv2与cv1相同,或者cv-qualification与cv1相同,并且 B既不是D的虚基类,也不是D的虚基类的基类。空指针值(4.10) 转换为目标类型的空指针值。

如果“指向cv1 B的指针”类型的prvalue指向实际上是D类型对象的子对象的B,则生成的指针指向封闭对象 类型为D. 否则,演员表的结果未定义。

由于&obj未指向DerivedForInt,因此它是UB。

答案 2 :(得分:2)

这仍然是未定义的行为,我相信它应该是。

为何未定义

由@ForEveR在他的回答中提供:

n3376 5.2.9 / 11

  

类型为“指向cv1 B的指针”的prvalue,其中B是类类型,可以转换为类型为“指向cv2 D的指针”的prvalue,其中D是从B派生的类(第10条)

     

...

     

如果“指向cv1 B的指针”类型的prvalue指向实际上是D类型对象的子对象的B,则生成的指针指向D类型的封闭对象。否则,结果为演员阵容未定义

为什么不应该定义

它只适用于POD类型,因为在您的基础中添加虚拟功能足以让您在我所知道的所有编译器中受到伤害。此外,类型之间的差异可能是概念性的,而不仅仅是数据布局。类型安全与提供强大的抽象同样重要,因为它可以防止数据表示问题。

如果您想要这样的东西,最好将它作为普通函数提供,或者在派生类中添加一个构造函数,该构造函数接受基类的实例。