You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写支持多继承的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 07:44:16