Can a destructor be recursive?

橙三吉。 提交于 2019-12-31 08:54:16

问题


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?


回答1:


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




回答2:


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:

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.




回答3:


It comes back to the compiler's definition of the lifetime of an object. As in, when is the memory really de-allocated. I would think it could not be until after the destructor has completed, as the destructor has access to the object's data. Therefore, I would expect recursive calls to the destructor to work.

But ... there are surely many ways to implement a destructor and the freeing of memory. Even if it worked as I wanted on the compiler I'm using today, I would be very cautious about relying on such behavior. There are lots of things where the documentation says it won't work or the results are unpredictable that in fact work just fine if you understand what is really happening inside. But it's bad practice to rely on them unless you really have to, because if the specs say that this doesn't work, then even if it really does work, you have no assurance that it will continue to work in the next version of the compiler.

That said, if you really want to call your destructor recursively and this isn't just a hypothetical question, why not just rip the entire body of the destructor into another function, let the destructor call that, and then let that call itself recursively? That should be safe.




回答4:


Yeah, that sounds about right. I would think once the destructor is finished calling, the memory would be dumped back into the allocatable pool, allowing something to write over it, thus potentially causing issues with follow-up destructor calls (the 'this' pointer would be invalid).

However, if the destructor doesn't finish until the recursive loop is unwound.. it should theoretically be fine.

Interesting question :)




回答5:


Why would anyone ever want to call the destructor recursively in this way ? Once you have called the destructor, it should destroy the object. If you call it again, you would be trying to initiate the destruction of an already partly destroyed object when you were still actually part way through actually destroying it at the same time.

All of the examples have some sort of decremental / incremental end condition, to essentially count down in calls, which is suggestive of some sort of failed implementation of a nested classs which contains members of the same type as itself.

For such a nested matryoshka class, calling the destructor on the members, recursively, ie the destructor calls the destructor on member A, which in turn calls the destructor on its own member A, which in turn calls the detructor ... and so on is perfectly fine and works exactly as one might expect. This is a recursive use of the destructor, but it is not recursively calling the destructor on itself which is insane, and would make almost no sense.



来源:https://stackoverflow.com/questions/3063341/can-a-destructor-be-recursive

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!