[英]When is a `static_cast<Base*>(static_cast<void*>(derived))` from a pointer to a derived class valid?
[英]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是类类型,可以转换为类型为“指向cv2D
指针”的prvalue,其中D
是从B
派生的类(第10条),如果是有效的标准从“指向D
指针”到“指向B
指针”的转换存在(4.10), cv2与cv1相同,或者cv资格比cv1更高 ,并且B
既不是D
的虚基类也不是基类D
的虚拟基类。 空指针值(4.10)将转换为目标类型的空指针值。 如果类型的prvalue“指针CV1B
”指向一个B
实际上是类型的对象的子对象D
,将所得指针指向类型的包围对象D
。 否则,行为未定义。
的问题,然后,是,是否在时间static_cast
,指针实际指向“一B
实际上是类型的对象的子对象D
”。 如果是这样,就没有UB; 如果不是,则无论是否取消引用或以其他方式使用结果指针 ,行为都是未定义的。
[class.dtor] / p15说(强调我的)
一旦为对象调用析构函数,该对象就不再存在
和[basic.life] / p1说的那样
类型
T
的对象的生命周期在以下情况下结束:
- 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者
- [...]
从那时起, D
对象的生命周期一旦被调用析构函数就结束了,当然也就是B
的析构函数开始执行时 - 这是在D
的析构函数体完成执行之后。 此时,没有“类型D
对象”,这个B
可以是 - 它“不再存在”的子对象。 因此,你有UB。
显然你的同事的印象是,只要你没有取消引用无效指针,你就没事了。
他错了 。
仅仅评估这样的指针具有未定义的行为。 这段代码显然已被破坏。
你应该明确告诉他这是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.