如何编写支持多继承的C++自定义删除器?
多继承场景下自定义operator delete的指针偏移问题与解决
问题重现
使用自定义分配器/释放器时,多继承场景下直接用基类指针调用operator delete会触发无效指针错误,代码示例如下:
#include <iostream> #include <memory> using namespace std; class Arena {}; void* operator new(std::size_t size, const Arena&) { auto ptr = malloc(size); cout << "new " << ptr << endl; return ptr; } void operator delete(void* ptr, const Arena&) { cout << "delete " << ptr << endl; free(ptr); } class A { public: virtual ~A() = default; }; class B { public: virtual ~B() = default; }; class AB : public A, public B { public: ~AB() override = default; }; int main() { B* ptr = new (Arena()) AB; ptr->~B(); operator delete(ptr, Arena()); return 0; }
运行输出:
new 0x55e20c8a6eb0 delete 0x55e20c8a6eb8 free(): invalid pointer
问题根源:多继承时,B*指向的是AB对象内部B子对象的起始地址(存在偏移),而非整个AB对象的原始分配地址。内置delete ptr能自动调整指针是因为虚析构函数的存在,编译器会在析构流程中完成指针偏移的还原。
核心问题:未知派生类时能否将B*转回原始对象指针?
可以,且有标准、不依赖实现的方案——利用**RTTI(运行时类型信息)**和dynamic_cast<void*>。
根据C++标准,dynamic_cast<void*>(ptr)会返回最底层派生对象的起始地址,无论当前指针指向的是哪个基类子对象。这个行为是标准规定的,不依赖编译器的具体实现,比依赖top_offset这类编译器扩展更可靠。
解决方案
方案1:手动转换指针后释放
直接用dynamic_cast<void*>获取原始分配地址,再调用自定义operator delete:
int main() { B* ptr = new (Arena()) AB; ptr->~B(); // 转换为整个对象的起始地址 void* original_ptr = dynamic_cast<void*>(ptr); operator delete(original_ptr, Arena()); // 正确释放原始地址 return 0; }
方案2:封装智能指针避免手动操作
更稳妥的方式是封装自定义智能指针,把析构和释放逻辑绑定,避免手动处理指针:
#include <iostream> #include <memory> using namespace std; class Arena {}; void* operator new(std::size_t size, const Arena&) { auto ptr = malloc(size); cout << "new " << ptr << endl; return ptr; } void operator delete(void* ptr, const Arena&) { cout << "delete " << ptr << endl; free(ptr); } class A { public: virtual ~A() = default; }; class B { public: virtual ~B() = default; }; class AB : public A, public B { public: ~AB() override = default; }; // 自定义删除器 template <typename T> struct ArenaDeleter { const Arena& arena; void operator()(T* ptr) const { ptr->~T(); // 自动转换到原始对象地址 operator delete(dynamic_cast<void*>(ptr), arena); } }; // 封装智能指针 template <typename T> using ArenaUniquePtr = unique_ptr<T, ArenaDeleter<T>>; int main() { Arena arena; // 直接使用智能指针,无需手动析构和释放 ArenaUniquePtr<B> ptr(new (arena) AB, {arena}); return 0; }
注意事项
- 必须确保基类(如示例中的
B)拥有虚析构函数,否则RTTI无法正常工作,dynamic_cast会失效。 - 避免手动调用析构函数和
operator delete,尽量用智能指针封装内存管理逻辑,减少出错概率。
内容的提问来源于stack exchange,提问作者bpmckinnon
相关产品推荐
相关产品推荐

