29

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

Chin
  • 19,717
  • 37
  • 107
  • 164
  • 14
    If you allocate with `new` but never deallocate with `delete` the destructor won't be called. – RamblingMad Nov 18 '14 at 04:29
  • 3
    Also, if you read the output of your demo more carefully; the error is caused by a call to a deleted **constructor** – RamblingMad Nov 18 '14 at 04:30
  • 1
    @CoffeeandCode main.cpp:9:12: error: use of deleted function 'A::~A()' It's exactly the destructor, not the constructor. –  Nov 18 '14 at 04:32
  • Not in the demo you posted, that is complaining that `B::B()` was implicitly deleted but still called; but it is caused by the deleted destructor. – RamblingMad Nov 18 '14 at 04:33
  • @CoffeeandCode No, I think that's not the true reason because it won't work if we declare the constructor implicitly. http://coliru.stacked-crooked.com/a/99a16549b4c21738 –  Nov 18 '14 at 04:35
  • Yeah, I was being pedantic; but semantically it shouldn't be calling the destructor of either class... so that's weird – RamblingMad Nov 18 '14 at 04:36
  • 5
    Why -1? It is a very interesting question, simple and concise, and I'd like very much to know the correct answer (I posted one but not 100% sure I got it right). – vsoftco Nov 18 '14 at 04:45
  • 1
    possible duplicate of [Deleted destructor in the class appeared as a virtual/direct base class or as a type of non-static data member](http://stackoverflow.com/questions/26923320/deleted-destructor-in-the-class-appeared-as-a-virtual-direct-base-class-or-as-a) – Rupesh Yadav. Nov 18 '14 at 05:21
  • What if you also delete `B::~B`? – user253751 Nov 18 '14 at 07:23
  • 3
    The fact wether a destructor IS called or MAY be called are two completely different issues. In your demo code the destructor is - for obvious reasons - not called. However, in order to make the code compilable, the base-class destructor A::~A() still has to exist. Hence, if you explicitly delete that function using the 'deletel' specifier, it won't compile, despite not being called. – emvee Nov 18 '14 at 09:27
  • @haavee: But can you find standard text to prove this? – Lightness Races in Orbit Nov 18 '14 at 13:15
  • So, can anyone claim to have found the absolute truth to this question? Or is the standard just ambiguous? I think it is a fundamental problem, and would love to know the truth value of the question (if it's not un-decidable as it looks now). – vsoftco Nov 19 '14 at 01:46
  • @vsoftco I think, T.C gave quite formal answer to that question. –  Nov 19 '14 at 04:29

6 Answers6

17

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.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 1
    @remyabel was looking at 12.4 also, you are probably right, it seems 12.4/7 does it. And also 12.4/8, indirectly: `After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members, the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. ` It cannot "call" a deleted destructor, so we get an error. – vsoftco Nov 18 '14 at 05:11
  • @remyabel I also got to say that I don't know if a deleted dtor means "defined", as in "defined as deleted", for the standard, although it wouldn't make too much sense. – vsoftco Nov 18 '14 at 05:15
  • And 8.4.3 makes it more ambiguous: `A function definition of the form is called a deleted definition` – vsoftco Nov 18 '14 at 05:22
  • These standard quotes do not exactly explain why `B::B()` would be ill-formed, do they? – ThomasMcLeod Nov 18 '14 at 05:43
16

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
11

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 1
    what if one marks the constructor as `noexcept`? the code doesn't compile in that case either. – vsoftco Nov 18 '14 at 19:29
  • 1
    @vsoftco `noexcept` doesn't tell the compiler that the constructor won't throw; it only specifies a particular behavior if it does,which ensures that the exception won't propagate. Knowing that no exception will propagate is very useful for the client code, but doesn't help the compiler any. – James Kanze Nov 19 '14 at 09:39
  • James, thanks for the clarification, I did not fully understand `noexcept` before. – vsoftco Nov 19 '14 at 18:44
5

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;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
2

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.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
0

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:)

Community
  • 1
  • 1
GMasucci
  • 2,834
  • 22
  • 42
  • in hindsight I answered this far too fast and missed the main thrust of the question, when I get some time I shall fix my answer. Cheers Barry for pointing this out:) – GMasucci Mar 15 '16 at 13:16