Gru
Gru

Reputation: 490

C++ Calling a destructor without freeing memory

Let's say that for whatever reason I wanted to call the destructor on an object without freeing memory. I understand this is doable with obj->~Type(), but let's say I have a void* and don't know the type statically. I guess then I would need 2 pointers, one for the object and one for the destructor.

However, I am curious if it is possible to get away with just one pointer in this case. Let's say that I can guarantee that the object will have a virtual destructor, and it will derive at most from one base class. Is it possible to reach the destructor through the v-table? And is that doable in a standard-compliant way (probably not)?

Upvotes: 0

Views: 875

Answers (2)

Dr. Gut
Dr. Gut

Reputation: 2866

You cannot get the address of a destructor. Quote from the C++17 standard:

The address of a destructor shall not be taken.

— See C++17 Latest Draft, [class.dtor]/2.

Solution with two pointers

However your two-pointer approach works. See the following example (live):

class Apple {};


struct Destructor {
public:
    template <class T>
    Destructor (const T* object) :
        object (object),
        dtor   ([] (const void* o) { static_cast<const T*> (o)->~T (); })
    {
    }

    void Call ()
    {
        dtor (object);
    }

private:
    const void*  object;
    void (*dtor) (const void*);
};


int main ()
{
    Apple* a = new Apple ();

    Destructor d (a);
    d.Call ();
}

The idea is taken from Herb Sutter's blog post. Remark: the memory pointed to by a is not freed in my example.

For the above to work you will have to know type Apple statically when you store the destructor. This is done when d is constructed. The type Apple is passed to the ctor. through template parameter T.

But you do not have to know Apple when you destroy it with d.Call ().

Solution with std::function

You can just as well call the destructor from an std::function.

#include <functional>

class Apple {};


int main ()
{
    Apple* a = new Apple ();

    std::function<void ()> d = [=] () { a->~Apple (); };
    d ();
}

Basically this does the same thing, as the previous approach. The pointer a is captured by the lambda, so it knows which Apple to destroy.

As previously when you are creating d you have to know Apple. It is in the lambda. If you call d (), you do not have to know the type.

Solution with one pointer (using a common base class)

struct Fruit { virtual ~Fruit () {} };
struct Apple : Fruit {};

Obviously you can call a dtor. of an Apple through a Fruit* pointer, if Fruit::~Fruit is virtual (otherwise it is UB). Similarly to the two-pointer solution, when you construct an object of Apple to be stored and destroyed later, you have to know the type (Fruit* f = new Apple ()), and when you destroy it, you do not have to know it (f->~Fruit ()).

Here f is a single pointer, but ends up using a second pointer, the vfptr (i.e. virtual function pointer), which points to an array of function pointers, one of which is the pointer to ~Apple. So three pointers are dereferenced for f->~Fruit ():

  • f,
  • the vfptr,
  • the pointer to ~Apple.

Solution with one pointer (no common base class)

It is impossible. Calling a destructor is a call instruction in assembly. For this you have to have two pointers:

  • the argument to pass (the this pointer), and
  • the address to call (the dtor.).

So even if someone has a one-pointer solution, it contains the second pointer through some indirection.

Upvotes: 1

MSalters
MSalters

Reputation: 180295

Let's assume a reasonable compiler which puts the dtor last in the vtable. After all, the dtor is rarely called - only once per object. You can't find it because the various vtables have different lengths.

Upvotes: 1

Related Questions