C++20销毁式delete能否安全延迟对象销毁?是否符合标准?
关于C++20 destroying delete的合规性与风险问题
假设库导出Foo类,客户端持有Foo*指针,使用完对象后需调用delete指针。希望在库内部让这些被调用delete的对象,在满足某些内部条件(如内部引用计数归零、异步操作完成)前保持存活,为此编写了基于C++20 destroying delete操作符的核心代码:
struct Foo { void real_delete_(Foo* ptr) { ptr->~Foo(); ::operator delete(ptr); } void operator delete(Foo* ptr, std::destroying_delete_t) { if (/*some condition to see if we can delete now*/) real_delete_(ptr); // otherwise real_delete_() will be called when condition gets met } };
同时编写了最小测试程序验证逻辑:
#include <cstdio> #include <memory> struct Foo { int refCount = 0; Foo() { printf("Foo created at %p\n", this); } ~Foo() { printf("Foo %p destroyed\n", this); } void incRef() { ++refCount; } void decRef() { --refCount; if (refCount == 0) realDelete(); } void realDelete() { this->~Foo(); ::operator delete(this); } void operator delete(Foo* ptr, std::destroying_delete_t) { if (ptr->refCount == 0) ptr->realDelete(); } }; int main() { auto foo = new Foo(); foo->incRef(); delete foo; foo->decRef(); }
该测试程序在GCC-14中可编译运行,Valgrind检测无错误,但在Clang-11中触发编译器内部错误。针对以下问题解答:
1. 上述代码是否符合C++标准,应当可编译运行吗?
符合C++20标准,应当可以编译运行。
C++20引入的destroying delete特性,核心就是允许自定义operator delete重载来接管对象销毁的完整流程:当调用delete ptr时,如果存在匹配的operator delete(T*, std::destroying_delete_t)重载,编译器不会自动调用对象的析构函数,而是将销毁对象和释放内存的责任完全交给这个重载函数。你的代码逻辑完全符合这一设计意图——在重载中判断条件,满足则立即销毁,不满足则延迟到条件达成时再手动执行析构和内存释放。
Clang-11触发内部错误属于编译器自身的bug,因为Clang-11对C20特性的支持尚不完善(该版本发布于C20正式定稿初期),升级到Clang-13及以上版本即可解决这个编译问题。
2. 此类让对象在delete调用后继续存活的代码是否定义良好?存在哪些风险?
代码的核心逻辑是标准允许的,但存在以下关键风险:
- 客户端语义误解风险:按照C++的常规认知,调用
delete后指针即成为悬垂指针,客户端不应该再访问。但你的逻辑中对象可能仍存活,若客户端不知情后续访问指针,虽然在技术上对象仍有效,但这违背了通用编程约定,极易引发客户端写出语义错误的代码。 - 线程安全隐患:如果多个线程同时操作引用计数、调用
delete或触发realDelete,未对refCount等共享状态做原子性保护的话,会出现竞态条件,导致重复销毁、内存泄漏或对象提前释放。 - 手动销毁的正确性风险:
realDelete中手动调用析构函数和全局operator delete,必须严格保证这些操作只执行一次,否则会触发重复析构或重复释放内存的未定义行为。 - 编译器兼容性问题:老版本编译器(如Clang-11、GCC-10及以下)对C++20 destroying delete的支持存在bug或不完整,若需要兼容这些环境,该写法会受限。
内容的提问来源于stack exchange,提问作者Always Confused
相关产品推荐
相关产品推荐

