C++20/23标准中重复析构与未析构复用存储的合规性问询
现代C++(C20/C23)中placement new与显式析构的违规操作及标准依据
1. 重复调用析构函数
alignas(T) std::byte storage[sizeof(T)]; T* const p = new (storage) T(...); p->~T(); p->~T();
在C20及C23标准中,无论T的析构函数是否平凡,上述代码均属于未定义行为。
标准条款依据
根据C20/C23标准的[class.dtor]/11条款:
一旦对象的析构函数被调用,该对象即不再存在;若对生命周期已结束的对象调用析构函数,行为是未定义的。[注:即使对象的析构函数是平凡的,此规则依然适用,因为析构函数的调用会销毁对象。——结束注]
C20修改了C17中对平凡析构函数重复调用的宽松规则,明确将其纳入未定义行为范畴:即使平凡析构不执行任何实际操作,调用析构函数的动作本身会标记对象生命周期的结束,后续对该“已销毁对象”的操作不再合法。
2. 未调用析构函数就复用存储
alignas(T) std::byte storage[sizeof(T)]; T* p = new (storage) T(...); p = new (storage) T(...); p->~T();
当T的析构函数非平凡时,上述代码属于未定义行为;若T的析构函数平凡,则该操作是允许的。
标准条款依据
根据C20/C23标准的[basic.life]/14条款:
若对象
o的生命周期已结束,且其占用的存储被复用或释放,程序必须确保o的析构函数被调用,除非满足以下任一条件:
- 对象是联合体的非当前成员;
- 对象的析构函数是平凡的;
- 对象类型为
T,且在o占用的存储中创建了一个T类型(或T的派生类型)的新对象,且T的析构函数是平凡的。
对于非平凡析构的T,直接在已有对象的存储上调用placement new创建新对象,会结束旧对象的生命周期,但未调用其析构函数,违反了上述条款的要求,因此行为未定义。而平凡析构的T满足例外条件,无需显式调用旧对象的析构函数即可复用存储。
内容的提问来源于stack exchange,提问作者jacobsa
相关产品推荐
相关产品推荐

