Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.


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.


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.


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;
}