Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a destructor be recursive?

Is this program well-defined, and if not, why exactly?

#include <iostream> #include <new> struct X {     int cnt;     X (int i) : cnt(i) {}     ~X() {               std::cout << "destructor called, cnt=" << cnt << std::endl;             if ( cnt-- > 0 )                 this->X::~X(); // explicit recursive call to dtor     } }; int main() {        char* buf = new char[sizeof(X)];     X* p = new(buf) X(7);     p->X::~X();  // explicit call to dtor     delete[] buf; } 

My reasoning: although invoking a destructor twice is undefined behavior, per 12.4/14, what it says exactly is this:

the behavior is undefined if the destructor is invoked for an object whose lifetime has ended

Which does not seem to prohibit recursive calls. While the destructor for an object is executing, the object's lifetime has not yet ended, thus it's not UB to invoke the destructor again. On the other hand, 12.4/6 says:

After executing the body [...] a destructor for class X calls the destructors for X's direct members, the destructors for X's direct base classes [...]

which means that after the return from a recursive invocation of a destructor, all member and base class destructors will have been called, and calling them again when returning to the previous level of recursion would be UB. Therefore, a class with no base and only POD members can have a recursive destructor without UB. Am I right?

like image 334
Cubbi Avatar asked Jun 17 '10 15:06

Cubbi


People also ask

Can destructor be declared as static?

Destructors cannot be declared const , volatile , const volatile or static .

Is destructor called automatically?

A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ). For example, the destructor for class String is declared: ~String() .

Can a destructor have parameters?

It never takes any parameters, and it never returns anything. You can't pass parameters to the destructor anyway, since you never explicitly call a destructor (well, almost never).


2 Answers

The answer is no, because of the definition of "lifetime" in §3.8/1:

The lifetime of an object of type T ends when:

— if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or

— the storage which the object occupies is reused or released.

As soon as the destructor is called (the first time), the lifetime of the object has ended. Thus, if you call the destructor for the object from within the destructor, the behavior is undefined, per §12.4/6:

the behavior is undefined if the destructor is invoked for an object whose lifetime has ended

like image 183
James McNellis Avatar answered Oct 20 '22 17:10

James McNellis


Okay, we understood that behavior is not defined. But let's do small journey into what really happends. I use VS 2008.

Here is my code:

class Test { int i;  public:     Test() : i(3) { }      ~Test()     {         if (!i)             return;              printf("%d", i);         i--;         Test::~Test();     } };  int _tmain(int argc, _TCHAR* argv[]) {     delete new Test();     return 0; } 

Let's run it and set a breakpoint inside destructor and let the miracle of recursion happen.

Here is stack trace:

alt text

What is that scalar deleting destructor? It is something that compiler inserts between delete and our actual code. Destructor itself is just a method, there is nothing special about it. It doesn't really release the memory. It is released somewhere inside that scalar deleting destructor.

Let's go to scalar deleting destructor and take a look at the disassembly:

01341580  mov         dword ptr [ebp-8],ecx  01341583  mov         ecx,dword ptr [this]  01341586  call        Test::~Test (134105Fh)  0134158B  mov         eax,dword ptr [ebp+8]  0134158E  and         eax,1  01341591  je          Test::`scalar deleting destructor'+3Fh (134159Fh)  01341593  mov         eax,dword ptr [this]  01341596  push        eax   01341597  call        operator delete (1341096h)  0134159C  add         esp,4  

while doing our recursion we are stuck at address 01341586, and memory is actually released only at address 01341597.

Conclusion: In VS 2008, since destructor is just a method and all memory release code are injected into middle function (scalar deleting destructor) it is safe to call destructor recursively. But still it is not good idea, IMO.

Edit: Ok, ok. The only idea of this answer was to take a look at what is going on when you call destructor recursively. But don't do it, it is not safe generally.

like image 21
Andrey Avatar answered Oct 20 '22 18:10

Andrey