[英]Safety of static_cast to pointer-to-derived class from base destructor
This is a variant of the questions Downcasting using the Static_cast in C++ and Safety of invalid downcast using static_cast (or reinterpret_cast) for inheritance without added members 这是使用C ++中的Static_cast进行Downcasting的问题的变体,以及使用static_cast(或reinterpret_cast)进行无效向下转换的安全性,无需添加成员
I am not clear on the phrase in the standard "B that is actually a subobject of an object of type D, the resulting pointer points to the enclosing object of type D" with respect to behavior in ~B. 我不清楚标准“B实际上是D类对象的子对象的短语,结果指针指向类型D的封闭对象”关于~B中的行为。 If you cast to D in ~B, is it still a subobject at that point ? 如果你在~B中转换为D,那么它仍然是一个子对象吗? The following simple example shows the question: 以下简单示例显示了以下问题:
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);
}
I know that the cast is an open door to disaster, and doing anything like this from the dtor is a bad idea, but a co-worker claims "The code is valid and works correctly. That cast is perfectly valid. The comment clearly states that it should not be dereferenced". 我知道演员是一个敞开大门的灾难,从dtor做这样的事情是一个坏主意,但同事声称“代码是有效的,并且工作正常。这个演员是完全有效的。评论明确指出它不应该被解除引用“。
I pointed out that the cast is unnecessary and we should prefer the protection provided by the type system to comments. 我指出演员阵容是不必要的,我们应该更喜欢类型系统提供的评论。 The sad part is that he is one of the senior/lead developers and a supposed c++ "expert". 令人遗憾的是,他是高级/首席开发人员之一,也是一位理想的c ++“专家”。
Can I tell him the cast is UB ? 我可以告诉他演员阵容是UB吗?
[expr.static.cast]/p11: [expr.static.cast] / P11:
A prvalue of type “pointer to cv1
B
,” where B is a class type, can be converted to a prvalue of type “pointer to cv2D
,” whereD
is a class derived (Clause 10) fromB
, if a valid standard conversion from “pointer toD
” to “pointer toB
” exists (4.10), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1 , andB
is neither a virtual base class ofD
nor a base class of a virtual base class ofD
. 类型为“指向cv1B
指针”的prvalue,其中B是类类型,可以转换为类型为“指向cv2D
指针”的prvalue,其中D
是从B
派生的类(第10条),如果是有效的标准从“指向D
指针”到“指向B
指针”的转换存在(4.10), cv2与cv1相同,或者cv资格比cv1更高 ,并且B
既不是D
的虚基类也不是基类D
的虚拟基类。 The null pointer value (4.10) is converted to the null pointer value of the destination type. 空指针值(4.10)将转换为目标类型的空指针值。 If the prvalue of type “pointer to cv1B
” points to aB
that is actually a subobject of an object of typeD
, the resulting pointer points to the enclosing object of typeD
. 如果类型的prvalue“指针CV1B
”指向一个B
实际上是类型的对象的子对象D
,将所得指针指向类型的包围对象D
。 Otherwise, the behavior is undefined. 否则,行为未定义。
The question, then, is whether, at the time of the static_cast
, the pointer actually points to "a B
that is actually a subobject of an object of type D
". 的问题,然后,是,是否在时间static_cast
,指针实际指向“一B
实际上是类型的对象的子对象D
”。 If so, there is no UB; 如果是这样,就没有UB; if not, then the behavior is undefined whether or not the resulting pointer is dereferenced or otherwise used . 如果不是,则无论是否取消引用或以其他方式使用结果指针 ,行为都是未定义的。
[class.dtor]/p15 says that (emphasis mine) [class.dtor] / p15说(强调我的)
Once a destructor is invoked for an object, the object no longer exists 一旦为对象调用析构函数,该对象就不再存在
and [basic.life]/p1 says that 和[basic.life] / p1说的那样
The lifetime of an object of type
T
ends when: 类型T
的对象的生命周期在以下情况下结束:
- if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or 如果T是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者
- [...] [...]
From this, then, the D
object's lifetime has ended as soon as its destructor is invoked, and certainly by the time B
's destructor began to execute - which is after D
's destructor body has finished execution. 从那时起, D
对象的生命周期一旦被调用析构函数就结束了,当然也就是B
的析构函数开始执行时 - 这是在D
的析构函数体完成执行之后。 At this point, there is no "object of type D
" left that this B
can be a subobject of - it "no longer exists". 此时,没有“类型D
对象”,这个B
可以是 - 它“不再存在”的子对象。 Thus, you have UB. 因此,你有UB。
Clang with UBsan will report an error on this code if B
is made polymorphic (given a virtual function), which supports this reading. 如果B
是多态的(给定一个虚函数),则支持UBsan将报告此代码的错误 ,这支持此读数。
Apparently your co-worker is under the impression that as long as you do not dereference an invalid pointer, you are fine. 显然你的同事的印象是,只要你没有取消引用无效指针,你就没事了。
He is wrong . 他错了 。
Merely evaluating such a pointer has undefined behaviour. 仅仅评估这样的指针具有未定义的行为。 This code is obviously broken. 这段代码显然已被破坏。
You should definitively tell him that it's UB ! 你应该明确告诉他这是UB! ! !
Why ? 为什么?
12.4/7: Bases and members are destroyed in the reverse order of the completion of their constructor The objects are detroyed in the reverse order of their constuction. 12.4 / 7:基础和成员按照构造函数完成的相反顺序销毁。对象按其构造的相反顺序销毁。
12.6.2/10: First (...) virtual base classes are initialized (...) then, direct base classes are initialized 12.6.2 / 10:初始化第一个(...)虚拟基类(...)然后,初始化直接基类
So when destructing a D, first the D members are destructed and then the D sub object, and only then will B be destructed. 因此,当破坏D时,首先破坏D成员然后破坏D子对象,然后才破坏B。
This code makes sure that f()
is called when the B object is destroyed: 此代码确保在销毁B对象时调用f()
:
~B() { f(this); }
So when a D object is destroyed, the D suboject is destroyed first, and then ~B() is executed, calling f()
. 因此,当D对象被销毁时,首先销毁D子对象,然后执行~B(),调用f()
。
In f()
you cast the pointer to a B as pointer to a D. This is UB: 在f()
您将指针转换为B作为指向D的指针。 这是UB:
3.8/5: (...) after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways . 3.8 / 5: (...)在对象的生命周期结束之后,在重用或释放对象占用的存储之前,可以使用任何指向对象所在或所在的存储位置的指针, 但只是在有限的方式 。 (...) The program has undefined behavior if the pointer is used to access a non-static data member or call a non-static member function of the object, or (...) the pointer is used as the operand of a static_cast . (...)如果指针用于访问非静态数据成员或调用对象的非静态成员函数,或者(...) 指针用作操作数,则程序具有未定义的行为 。 static_cast 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.