Why is a destructor called if it's deleted and not called if it's not deleted?

泄露秘密 提交于 2019-12-09 05:01:28

问题


Consider the following code:

#include <iostream>

struct A 
{
    A(){ };
    ~A(){ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

B *b = new B; //Doesn't produce any side-effect.

int main(){ }

DEMO

The program doesn't produce any output which means the destructor isn't being called. But if we replace the destructor's body with the delete specifier, the program won't even compile.

#include <iostream>

struct A 
{
    A(){ };
    ~A() = delete; //{ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

B *b = new B; //Error: use of deleted function

int main(){ }

DEMO

due to call to the deleted function. That's the destructor that is being called in that case. Why is there such a difference?

It won't work even if we define B's constructor explicitly:

#include <iostream>

struct A 
{
    A(){ };
    ~A() = delete; //{ std::cout << "~A::A()" << std::endl; };
};

struct B: A 
{ 
    B(){ };
};

B *b = new B;

int main(){ }

DEMO


回答1:


The applicable parts of the standard are (quoting N4140):

§12.4 [class.dtor]/p11:

A destructor is potentially invoked if it is invoked or as specified in 5.3.4 and 12.6.2. A program is ill-formed if a destructor that is potentially invoked is deleted or not accessible from the context of the invocation.

§12.6.2 [class.base.init]/p10:

In a non-delegating constructor, the destructor for each potentially constructed subobject of class type is potentially invoked (12.4). [ Note: This provision ensures that destructors can be called for fully-constructed sub-objects in case an exception is thrown (15.2). —end note ]

§12 [special]/p5:

For a class, its non-static data members, its non-virtual direct base classes, and, if the class is not abstract (10.4), its virtual base classes are called its potentially constructed subobjects.

Since A is a non-virtual direct base of B and hence a potentially constructed subobject of B, its destructor is potentially invoked in B::B(), and since that destructor is deleted, the program is ill-formed.

See also CWG issue 1424.




回答2:


The problem is that the constructor of B is deleted by the compiler, as otherwise the default definition would be ill-formed. That's because A has no destructor, and the default constructor of B cannot construct a B from A if A cannot be destroyed. That's the error you are getting if you compile with g++:

note: 'B::B()' is implicitly deleted because the default definition would be ill-formed:

or clang++:

error: call to implicitly-deleted default constructor of 'B'

And if you declare the constructor B(){} explicitly, then it complains because it cannot destroy the A part of B, due to the deletion of A::~A(). The ability of B to bury its parent A is checked at compile time, so you are getting an error.

+1 for the question. It seems that you cannot inherit (then use an instance) from a class with a deleted destructor, although it is the first time I'm bumping into this issue.




回答3:


Concerning what I think is your basic question: why you cannot construct a B, even though it is only the destructor of A which doesn't exist: In the constructor of B, the compiler automatically generates code to call the destructor of A if there is an exception. If A is to be used as a base class, it must have an accessible destructor (public or protected).

In your case, of course, the constructor of B cannot throw, and so A::~A() will never actually be called. But the compiler can't always determine if this is the case or not, and the standard doesn't require it to even try. The compiler must assume that the body of B::B() may throw (after having completely constructed A). And even if it can determine that B::B() cannot throw, and does not generate the code to call the destructor of A, this is an optimization, which is not allowed to change the legality of the code.




回答4:


Based on the first code:

#include <iostream>
struct A
{
  A(){ };
  ~A(){ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

B *b = new B; //Doesn't produce any side-effect.

int main(){ }
  1. Object b is declared as global, so its lifecycle is as long as program runs.
  2. Object b is allocated dynamically, so 'naked' deletion is needed.

Try the following:

#include <iostream>

struct A 
{
    A(){ };
    ~A(){ std::cout << "~A::A()" << std::endl; };
};

struct B: A { };

int main(){
    B b;
}



回答5:


C++14 is clear about this:

[C++14: 12.6.2/10]: In a non-delegating constructor, the destructor for each potentially constructed subobject of class type is potentially invoked (12.4). [ Note: This provision ensures that destructors can be called for fully-constructed sub-objects in case an exception is thrown (15.2). —end note ]

There is no such wording in C++11 as it was added in issue 1424, but since this fact cannot be ignored, it is also true in practice in C++11 and C++98/03 implementations.

So, although you're still not ever invoking the destructor, the presence of inheritance means B() requires ~A() to be invokable: basically, that means accessible and not deleted.




回答6:


In answer to your headline question: if you don't call delete then no deletion takes place, although the memory utilised by the object can be freed by the termination of the program which created it, which can cause one of several things to happen:

  • memory is marked as free/available by the OS, as the owning process has terminated, and the memory is then available to the OS/other processes again
  • the destructors for all objects are called, either implicit destructors or explicit where declared, as the main process terminates (and the memory is then available to the OS/other processes again).
  • sheer raw willpower from the original code transcends programmatic function and obliterates the contents of memory allocated by the un-deleted objects and returns the memory to the OS.

Well, other than the last one, you get the gist, the last is merely a fond sentimentality to bolster a coders ego:D

The only provisos to this would be orphaned objects (where the pointer to a memory location is lost at some point in the code, see here for example) and object with a deleted destructor(see here for a brief explanation): these can't be deleted because in the first instance they are no longer addressable, and in the second they are an erroneous* object as there is no destructor for them (* erroneous as in not conforming to standard use/spec, however there are times when you may want to stop an object from being deleted on its own/before the owning process has terminated, for example a special case singleton, however, good programming/logic should prevent the need for a destructor being unavailable at all).

Let me know if you need more information, or if any of the above needs clarification, and I shall be only too happy to help:)



来源:https://stackoverflow.com/questions/26986570/why-is-a-destructor-called-if-its-deleted-and-not-called-if-its-not-deleted

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