如何编写一个支持多重继承的自定义删除器?

bpm*_*non 9 c++ memory multiple-inheritance

我有一个程序,它使用自定义分配器和解除分配器来管理内存。我最近遇到了一个泄漏,它使我陷入了一个巨大的兔子洞,最终导致自定义删除器无法处理多重继承。在下面的代码示例中:

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

new 0x55e20c8a6eb0
delete 0x55e20c8a6eb8
free(): invalid pointer
Run Code Online (Sandbox Code Playgroud)

因为B的地址是AB内部某处的vtable。使用内置delete ptr函数会导致指针返回到其原始值并成功释放。我在这里找到了一些有关 top_offset 的信息用于解决此问题,但这取决于实现。那么,有没有一种方法可以在不了解 AB 的情况下将指向 B 的指针转换回指向 AB 的指针呢?

n. *_* m. 9

你可以这样做:

void* dptr = dynamic_cast<void*>(ptr);
ptr->~B();
operator delete(dptr, Arena());
Run Code Online (Sandbox Code Playgroud)

现场演示

请注意,您需要在销毁对象之前B进行dynamic_cast 。

如果没有 RTTI,事情就会变得棘手。我假设在您的真实代码中,您需要 arena 对象的标识(否则定义成员运算符 new/delete 会很简单,它只是凭空拉出一个 arena 并重定向到全局放置 new/delete )。您需要将此身份存储在某处。嗯,如果我们只能为其动态分配一些内存就好了……等一下……我们正在分配内存,我们可以将它存储在那里,只需适当增加大小即可……

union AlignedArenaPtr {
  Arena* arena;
  std::max_align_t align;
};

struct Base { // inherit everything from this

    virtual ~Base() = default;

    void* operator new(std::size_t size, Arena *arena) {
        auto realPtr = (AlignedArenaPtr*)::operator new(size + 
            sizeof(AlignedArenaPtr), arena);
        realPtr->arena = arena;
        return realPtr + 1;
    }

    void operator delete(void* ptr) {
       auto realPtr = ((AlignedArenaPtr*)(ptr)) - 1;
       ::operator delete(realPtr, realPtr->arena);
    }

    void* operator new(std::size_t size) = delete; // just in case
};
Run Code Online (Sandbox Code Playgroud)

现场演示

  • @RyanHaining `dynamic_cast&lt;void*&gt;` 返回指向最派生对象的指针。换句话说,就是“在不了解 AB 的情况下将指向 B 的指针转换回指向 AB 的指针”。 (4认同)
  • `dynamic_cast` 的 TIL [“如果 T 是“指向 cv void 的指针”,则结果是指向 v 指向的最派生对象的指针。”](https://timsong-cpp.github.io/cppwp/ expr.dynamic.cast#7) (4认同)