Can a destructor be recursive?

Is this program correct, 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 calling the destructor twice is undefined behavior , according to 12.4 / 14, it definitely says the following:

undefined behavior if the destructor is called for an object whose lifetime has expired

Which, apparently, does not prohibit recursive calls. While the destructor for the object is running, the lifetime of the object has not yet expired, so it is not UB that calls the destructor again. On the other hand, 12.4 / 6 reads:

After executing the body [...] a destructor for class X calls destructors for direct elements of X, destructors for direct base X classes [...]

which means that after returning from the recursive call of the destructor, all destructors of the class and class will be called, and their repetition when returning to the previous level of recursion will be UB. Therefore, a class without basic and only POD elements can have a recursive destructor without UB. I'm right?

+50
c ++ destructor
Jun 17 '10 at 15:55
source share
5 answers

The answer is no, due to 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 nontrivial destructor (12.4), the call to the destructor begins or

- The storage that the object occupies is reused or freed.

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

undefined behavior if the destructor is called for an object whose lifetime has expired

+60
Jun 17 '10 at 16:01
source share

Well, we realized that the behavior is undefined. But let's take a little trip into what really happens. I am using 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 the destructor, and let the miracle of recursion happen.

Here is the stack trace:

alt text

What is scalar deleting destructor ? This is what the compiler inserts between delete and our real code. The destructor itself is just a method, there is nothing special about it. It really does not free memory. It is released somewhere inside this scalar deleting destructor .

Go to scalar deleting destructor and 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 

during our recursion, we were stuck at 01341586 , and the memory is actually freed only at 01341597 .

Conclusion: in VS 2008, since the destructor is just a method, and the entire memory deallocation code is entered into the middle function ( scalar deleting destructor ), it is safe to call the destructor recursively. But, nevertheless, this is not a good idea, IMO.

Edit : Good, good. The only idea of ​​this answer was to take a look at what happens when you call the destructor recursively. But do not do this, it is not safe at all.

+9
Jun 17 '10 at 16:28
source share

Returns to the compiler description of the lifetime of the object. As in the case when the memory is really de-allocated. I think this is not possible until after the destructor completes, since the destructor does not have access to the object data. Therefore, I expect recursive calls to the destructor to work.

But ... there are many ways to implement a destructor and free up memory. Even if it worked as I wanted in the compiler that I use today, I would be very careful about this behavior. There are many things where the documentation says that this will not work or the results are unpredictable, which actually works great if you understand what is actually going on inside. But it’s bad practice to rely on them, if you really don’t need it, because if the specifications say that it will not work, then even if it really works, you are not sure that it will continue to work in the next version of the compiler .

However, if you really want to call your destructor recursively, and this is not just a hypothetical question, why not just break the entire array of the destructor into another function, let the destructor call it, and then let this call itself recursively? It should be safe.

+5
Jun 17 '10 at 17:06
source share

Yes, that sounds right. I think that as soon as the destructor is completed, the memory will be flushed back to the allocated pool, which will allow something to be written above it, which could potentially cause problems with subsequent calls to the destructor (the 'this' pointer will be invalid).

However, if the destructor does not end until the recursive loop is unwound. Theoretically, this will be normal.

Interest Ask:)

+1
Jun 17 '10 at 16:00
source share

Why would anyone ever want to call a recursively destructor this way? Once you have called the destructor, it must destroy the object. If you call it again, you will try to initiate the destruction of the already partially destroyed object when you still find part of the path, effectively destroying it at the same time.

All examples have some decrement / incremental final condition, essentially count in calls, which suggests some unsuccessful implementation of nested classes that contain members of the same type as themselves.

For such a nested matryoshka class that calls the destructor on elements, it is recursive, i.e. the destructor calls the destructor on member A, which in turn calls the destructor on its own element A, which in turn calls detructor ... etc. Works great and works exactly as you would expect. This is a recursive use of the destructor, but it does not recursively call the destructor on itself, which is crazy and almost makes no sense.

0
Aug 05 '17 at 23:57 on
source share



All Articles