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

Are there cases where downcasting an actual Base to a Derived would be defined?

本文关键字:转换 Derived 情况 Base 存在 是否      更新时间:2023-10-16

在一般情况下,从(动态)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是出于说明目的的随机选择,如果与其他演员一起工作,也很有趣!

好吧,我可能会把这个答案切成碎片。。。

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

在这些条件下,你的演员阵容应该不会造成任何麻烦,即使是UB。根据其中一个消息来源,进行至少是安全的

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

来源:

什么是聚合和POD,它们是如何/为什么特别的?

POD和C++11中的继承。结构==的地址是第一个成员的地址吗?

来自第二个链接:

以下是标准第9节【类别】中的定义:

标准布局类是指:

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

然后你想要的财产得到保证(第9.2节[class.mem]):

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

这实际上比旧的要求要好,因为添加非平凡的构造函数和/或析构函数不会丢失重新解释_ ast的能力。

n3376 5.2.9/11

"pointer to cv1 B"类型的prvalue,其中B是类类型,可以转换为"pointer到cv2D,"其中D是从B派生的类(第10条),如果从"指针到D"的有效标准转换对于"指向B的指针"的存在(4.10),cv2与cv1具有相同的cv资格,或者比cv1具有更大的cv资质,并且B既不是D的虚拟基类,也不是D的虚基类的基类。空指针值(4.10)被转换为目标类型的空指针值。

如果类型为"pointer to cv1 B"的prvalue指向一个B,该B实际上是类型为D的对象的子对象,则得到的指针指向封闭对象类型为D。否则,强制转换的结果是未定义的

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

这仍然是未定义的行为,我认为应该是。

为什么未定义

正如@ForEverR在他的回答中提供的:

n3376 5.2.9/11

类型为"pointer to cv1 B"的prvalue,其中B是类类型,可以转换为类型为"pointer to cv2 D"的prvalue,其中D是从B 派生的类(第10条)

如果类型"pointer to cv1 B"的prvalue指向实际上是D类型对象的子对象的B,则结果指针指向D类型的封闭对象。否则,强制转换的结果是未定义的

为什么应该未定义

它只适用于POD类型,因为在你的基础上添加一个虚拟函数就足以伤害我所知道的所有编译器。此外,类型之间的差异可能是概念上的,而不仅仅是数据布局上的差异。类型安全与防止数据表示问题一样,也与提供强抽象有关。

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