繁体   English   中英

static_cast到base析构函数的指向派生类的安全性

[英]Safety of static_cast to pointer-to-derived class from base destructor

这是使用C ++中的Static_cast进行Downcasting的问题的变体,以及使用static_cast(或reinterpret_cast)进行无效向下转换的安全性,无需添加成员

我不清楚标准“B实际上是D类对象的子对象的短语,结果指针指向类型D的封闭对象”关于~B中的行为。 如果你在~B中转换为D,那么它仍然是一个子对象吗? 以下简单示例显示了以下问题:

void f(B* b);

class B {
public:
  B() {}
  ~B() { f(this); }
};

class D : public B { public: D() {} };

std::set<D*> ds;

void f(B* b) {
  D* d = static_cast<D*>(b);  // UB or subobject of type D?
  ds.erase(d);
}

我知道演员是一个敞开大门的灾难,从dtor做这样的事情是一个坏主意,但同事声称“代码是有效的,并且工作正常。这个演员是完全有效的。评论明确指出它不应该被解除引用“。

我指出演员阵容是不必要的,我们应该更喜欢类型系统提供的评论。 令人遗憾的是,他是高级/首席开发人员之一,也是一位理想的c ++“专家”。

我可以告诉他演员阵容是UB吗?

[expr.static.cast] / P11:

类型为“指向cv1 B指针”的prvalue,其中B是类类型,可以转换为类型为“指向cv2 D指针”的prvalue,其中D是从B派生的类(第10条),如果是有效的标准从“指向D指针”到“指向B指针”的转换存在(4.10), cv2cv1相同,或者cv资格比cv1更高 ,并且B既不是D的虚基类也不是基类D的虚拟基类。 空指针值(4.10)将转换为目标类型的空指针值。 如果类型的prvalue“指针CV1 B ”指向一个B实际上是类型的对象的子对象D ,将所得指针指向类型的包围对象D 否则,行为未定义。

的问题,然后,是,是否在时间static_cast ,指针实际指向“一B实际上是类型的对象的子对象D ”。 如果是这样,就没有UB; 如果不是,则无论是否取消引用或以其他方式使用结果指针 ,行为都是未定义的。

[class.dtor] / p15说(强调我的)

一旦为对象调用析构函数,该对象就不再存在

和[basic.life] / p1说的那样

类型T的对象的生命周期在以下情况下结束:

  • 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者
  • [...]

从那时起, D对象的生命周期一旦被调用析构函数就结束了,当然也就是B的析构函数开始执行时 - 这是在D的析构函数体完成执行之后。 此时,没有“类型D对象”,这个B可以是 - 它“不再存在”的子对象。 因此,你有UB。

如果B是多态的(给定一个虚函数),则支持UBsan将报告此代码的错误 ,这支持此读数。

显然你的同事的印象是,只要你没有取消引用无效指针,你就没事了。

错了

仅仅评估这样的指针具有未定义的行为。 这段代码显然已被破坏。

你应该明确告诉他这是UB!

为什么?

12.4 / 7:基础和成员按照构造函数完成的相反顺序销毁。对象按其构造的相反顺序销毁。

12.6.2 / 10:初始化第一个(...)虚拟基类(...)然后,初始化直接基类

因此,当破坏D时,首先破坏D成员然后破坏D子对象,然后才破坏B。

此代码确保在销毁B对象时调用f()

 ~B() { f(this); } 

因此,当D对象被销毁时,首先销毁D子对象,然后执行~B(),调用f()

f()您将指针转换为B作为指向D的指针。 这是UB:

3.8 / 5: (...)在对象的生命周期结束之后,在重用或释放对象占用的存储之前,可以使用任何指向对象所在或所在的存储位置的指针, 但只是在有限的方式 (...)如果指针用于访问非静态数据成员或调用对象的非静态成员函数,或者(...) 指针用作操作数,则程序具有未定义的行为 。 static_cast

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM