Why is the destructor not called in operator delete?
Asked Answered
P

3

18

I tried to call ::delete for a class in the operator delete of it. But the destructor is not called.

I defined a class MyClass whose operator delete has been overloaded. The global operator delete is also overloaded. The overloaded operator delete of MyClass will call the overloaded global operator delete.

class MyClass
{
public:
    MyClass() { printf("Constructing MyClass...\n"); }
    virtual ~MyClass() { printf("Destroying MyClass...\n"); }

    void* operator new(size_t size)
    {
        printf("Newing MyClass...\n");
        void* p = ::new MyClass();
        printf("End of newing MyClass...\n");
        return p;
    }

    void operator delete(void* p)
    {
        printf("Deleting MyClass...\n");
        ::delete p;    // Why is the destructor not called here?
        printf("End of deleting MyClass...\n");
    }
};

void* operator new(size_t size)
{
    printf("Global newing...\n");
    return malloc(size);
}

void operator delete(void* p)
{
    printf("Global deleting...\n");
    free(p);
}

int main(int argc, char** argv)
{
    MyClass* myClass = new MyClass();
    delete myClass;

    return EXIT_SUCCESS;
}

The output is:

Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...

Actual:

There is only one call to the destructor before calling the overloaded operator delete of MyClass.

Expected:

There are two calls to the destructor. One before calling the overloaded operator delete of MyClass. Another before calling the global operator delete.

Piecemeal answered 21/10, 2019 at 9:47 Comment(8)
MyClass::operator new() should allocate raw memory, of (at least) size bytes. It should not attempt to completely construct an instance of MyClass. The constructor of MyClass is executed after MyClass::operator new(). Then, the delete expression in main() calls the destructor, and releases the memory (without calling the destructor again). The ::delete p expression has no information about the type of object p points at, since p is a void *, so cannot invoke the destructor.Tolu
Related: https://mcmap.net/q/246354/-how-to-properly-free-the-memory-allocated-by-placement-newCatalase
The responses already given to you are correct, but I wonder: why are you trying to override new and delete? The typical use case is implementing custom memory management (GC, memory that doesn't come from the default malloc(), etc). Maybe you're using the wrong tool for what you're trying to achieve.Downtime
::delete p; causes undefined behaviour since the type of *p is not the same as the type of the object being deleted (nor a base class with virtual destructor)Wirephoto
@Wirephoto The major compilers do only warn on it at most, so I didn't realize that void* as operand is even explicitly ill-formed. [expr.delete]/1: "The operand shall be of pointer to object type or of class type. [...] This implies that an object cannot be deleted using a pointer of type void because void is not an object type.*" @OP I have amended my answer.Raceway
Here is a version that works: onlinegdb.com/Hy6jr9IqSPuppis
@uneven_mark apparently void * is an "object pointer type", but not a "pointer to object type", according to [basic.compound]/3. Clear as mud...Wirephoto
@Wirephoto The standard is also mixing the spellings "pointer to object type" and "pointer-to-object type". I hope they mean the same...Raceway
R
19

You are misusing operator new and operator delete. These operators are allocation and deallocation functions. They are not responsible for constructing or destructing objects. They are responsible only for providing the memory in which the object will be placed.

The global versions of these functions are ::operator new and ::operator delete. ::new and ::delete are new/delete-expressions, as are new/delete, differing from those, in that ::new and ::delete will bypass class-specific operator new/operator delete overloads.

The new/delete-expressions construct/destruct and allocate/deallocate (by calling the appropriate operator new or operator delete before construction or after destruction).

Since your overload is only responsible for the allocation/deallocation part, it should call ::operator new and ::operator delete instead of ::new and ::delete.

The delete in delete myClass; is responsible for calling the destructor.

::delete p; does not call the destructor because p has type void* and therefore the expression cannot know what destructor to call. It will probably call your replaced ::operator delete to deallocate the memory, although using a void* as operand to a delete-expression is ill-formed (see edit below).

::new MyClass(); calls your replaced ::operator new to allocate memory and constructs an object in it. The pointer to this object is returned as void* to the new-expression in MyClass* myClass = new MyClass();, which will then construct another object in this memory, ending the lifetime of the previous object without calling its destructor.


Edit:

Thanks to @M.M's comment on the question, I realized that a void* as operand to ::delete is actually ill-formed. ([expr.delete]/1) However, the major compilers seem to have decided to only warn about this, not error. Before it was made ill-formed, using ::delete on a void* had already undefined behavior, see this question.

Therefore, your program is ill-formed and you don't have any guarantee that the code actually does what I described above if it still managed to compile.


As pointed out by @SanderDeDycker below his answer, you also have undefined behavior because by constructing another object in the memory that already contains a MyClass object without calling that object's destructor first you are violating [basic.life]/5 which forbids doing so if the program depends on the destructor's side effects. In this case the printf statement in the destructor has such a side effect.

Raceway answered 21/10, 2019 at 10:17 Comment(1)
The misusing is intended to check how these operator works. However, thanks for your answer. It seems the only answer that solves my problem.Piecemeal
E
13

Your class-specific overloads are done incorrectly. This can be seen in your output: the constructor is called twice!

In the class-specific operator new, call the global operator directly:

return ::operator new(size);

Similarly, in the class-specific operator delete, do:

::operator delete(p);

Refer to the operator new reference page for more details.

Erwinery answered 21/10, 2019 at 10:19 Comment(2)
I know the constructor is called twice by calling ::new in operator new and I mean it. My question is why destructor is not called when calling ::delete in operator delete?Piecemeal
@Piecemeal : Intentionally calling the constructor a second time without calling the destructor first is a really bad idea. For non-trivial destructors (like yours), you're even venturing into undefined behavior territory (if you depend on the destructor's side effects, which you do) - ref. [basic.life] §5. Don't do this.Erwinery
H
1

See CPP Reference:

operator delete, operator delete[]

Deallocates storage previously allocated by a matching operator new. These deallocation functions are called by delete-expressions and by new-expressions to deallocate memory after destructing (or failing to construct) objects with dynamic storage duration. They may also be called using regular function call syntax.

Delete (and new) are only responsible for the 'memory management' part.

So it is clear and expected that the destructor is only called once - to clean up the instance of the object. Would it be called twice, every destructor would have to check if it had already been called.

Hyponasty answered 21/10, 2019 at 9:58 Comment(6)
The delete operator should still implicitly call the destructor, as you can see from his own logs showing the destructor after deleting the instance. The issue here is that his class delete override is calling ::delete which leads to his global delete override. That global delete override merely frees memory so it won't reinvoke the destructor.Guttapercha
The reference states clearly that delete is called AFTER object deconstructionHyponasty
Yes, the global delete is called after the class delete. There are two overrides here.Guttapercha
@PickleRick - while it is true that a delete expression should call a destructor (assuming supplied a pointer to a type with a destructor) or a set of destructors (array form), an operator delete() function is not the same thing as a delete expression. The destructor is called before the operator delete() function is called.Tolu
I'm aware of that, maybe there's a misunderstanding here? I'm only saying that the reason it wasn't destructed twice (which is what he's asking) is because the class delete is not the same as the global delete, which in his case is just going to free the memory.Guttapercha
It would help if you added a header to the qoute. Currently it is not clear what it refers to. "Dellocates storage..." - who deallocates storage?Flores

© 2022 - 2024 — McMap. All rights reserved.