Home > front end >  How to write a custom deleter that works with multiple inheritance?
How to write a custom deleter that works with multiple inheritance?

Time:09-02

I have a program which uses a custom allocator and deallocator to manage memory. I've recently encountered a leak that has lead me down a huge rabbit hole that end with custom deleters being incapable of handling multiple inheritance. In the code sample below:

#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;
}

The output is:

new 0x55e20c8a6eb0
delete 0x55e20c8a6eb8
free(): invalid pointer

Because the address of B is a vtable somewhere inside AB. Using the builtin delete ptr function leads to the pointer being returned to it's original value and freed successfully. I've found some information about top_offset being used to address this here, but this is implementation dependent. So, is there a way to convert a pointer to B back into a pointer to AB without knowing anything about AB?

CodePudding user response:

You can do it this way:

void* dptr = dynamic_cast<void*>(ptr);
ptr->~B();
operator delete(dptr, Arena());

Live demo

Note you need to dynamic_cast before destroying the B object.

Without RTTI things get hairy. I assume that in your real code you need the identity of the arena object (otherwise it would be trivial to define member operators new/delete that just pull an arena out of thin air and redirect to global placement new/delete). You need to store this identity somewhere. Hmm, if we only could dynamically allocate some memory for it... wait a minute... we are allocating memory, we can store it there, just increase the size appropriately...

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
};

Live demo

  • Related