这是问题Downcasting using the Static_cast in C++和Safety of invalid downcast using static_cast (or reinterpret_cast) for inheritance without added members
的变体我不清楚标准" B中的短语实际上是D类型对象的子对象,结果指针指向D&#34型的封闭对象;关于~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做这样的事情是一个坏主意,但同事声称&#34;代码是有效的,并且正常工作。演员表是完全有效的。评论明确指出不应该取消引用&#34;。
我指出演员阵容是不必要的,我们应该更喜欢类型系统提供的评论。令人遗憾的是,他是高级/首席开发人员之一,也是一位经验丰富的c ++&#34;专家&#34;。
我可以告诉他演员阵容是UB吗?
答案 0 :(得分:5)
[expr.static.cast] / P11:
类型为“指向 cv1
B
的指针的prvalue,”其中B是类类型,可以 转换为“指向 cv2D
的指针”的prvalue,其中D
是 如果是有效的标准转换,则从B
派生类(第10条) 从“指向D
”到“指向B
的指针”存在(4.10), cv2 是 与 cv1 相同的cv-qualification,或更高的cv资格,和B
既不是D
的虚基类,也不是a的基类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
的析构函数开始执行时 - {{1}之后析构函数体已完成执行。此时,没有“D
类型的对象”,这个D
可以是 - 它“不再存在”的子对象。因此,你有UB。
如果B
是多态的(给定虚函数),则支持UBsan将report an error使用此代码。
答案 1 :(得分:2)
显然你的同事的印象是,只要你没有取消引用无效指针,你就可以了。
他错误。
仅仅评估这样的指针有未定义的行为。这段代码显然已被破坏。
答案 2 :(得分:0)
你应该明确地告诉他,这是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:(...)在对象的生命周期结束后,在重用或释放对象占用的存储空间之前, 指向对象所在存储位置的指针 或被找到 可能会被使用,但只能以有限的方式使用。 (...)如果指针用于访问非静态数据成员或调用非静态成员函数,则程序具有未定义行为 object,或(...)指针用作static_cast 的操作数。