Question

I realise this is ill-advised, and I'm not proposing to do it, but I'm curious as to whether the following is actually formally illegal:

#include <iostream>

struct X
{
    ~X()
    {
        std::cout << "~X()\n";
    }
};

int main()
{
    X *x = new X;
    //delete x;
    x->~X();
    ::operator delete(x);
    return 0;
}

It's my understanding that delete x; is equivalent to invoking the destructor and then calling ::operator delete(x);, but is it legal for me to do that manually according to the standard? I know this is a valid thing to when using placement new, but what about in the non-placement case? My hunch is that it might be illegal because delete (and not operator delete) has to be performed for each new, but I'd be interested to know for sure.

Was it helpful?

Solution

I'm pretty sure that this is not standard compliant. Nowhere in the standard (that I'm aware of) does it say that the operand of delete p; is passed directly to the deallocation function, and for delete [] it almost certainly isn't passed through unchanged.

It will probably work on all practical implementations, however.

For certain, you shouldn't be calling the global deallocation function explicitly. It's fine in your example, which doesn't have user-defined allocation and deallocation functions, but not in the general case (actually, is there any syntax for calling the class-specific deallocation function if it exists, and the global one otherwise?).

Also, in the general case, the pointer passed to the deallocation function is definitely not the operand of delete. Consider:

struct A
{
  virtual ~A() {}
};

struct B1 : virtual A {};

struct B2 : virtual A {};

struct B3 : virtual A {};

struct D : virtual B1, virtual B2, virtual B3 {};

struct E : virtual B1, virtual D {};

int main( void )
{
  B3* p = new D();
  p->~B3(); // should be ok, calling virtually
  operator delete(p); // definitely NOT OK

  return 0;
}

OTHER TIPS

If you are going this way, you should replace

X *x = new X;

by

X* x = static_cast<X*>(::operator new(sizeof(X)));

try { new(x) X; }
catch (...) { ::operator delete(x); throw; }

which should be (please correct me if I'm wrong) consistant with your destruction approach, and pretty much equivalent in functionality to new.

I believe that you don't invoke undefined behaviour if you call the destructor and free the memory.

@StuartGolodetz

Answer, and reply to a comment

There may well be some template metaprogramming one can do to make sure the global one is called otherwise, but I'm not an expert on that. It's probably an SFINAE thing of some sort if it is possible I'd guess.

struct B {
    virtual ~B();
};

struct D : B {
    operator new (size_t);
    operator delete (void*);
};

B *p = new D;

decltype(typeid(*p))::operator delete (...); // hypothetical C++++

Meta this!

I thought that

::operator delete(x);

is the same as

delete x;

if the delete method is not overridden by X?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top