5

In Bjarne Stroustrup's "The C++ Programming Language (4th edition)" in section 17.6 (Generating Default Operations) it mentions this:

If the programmer declares a copy operation, a move operation, or a destructor for a class, no copy operation, move operation, or destructor is generated for that class.

Thus, I'm confused why the SubObj destructor is called in this program:

#include <iostream>
using namespace std;

class SubObj {
    public:
        ~SubObj() {
            cout << "SubObj Destructor called" << endl;
        }
};

class Obj {
    private:
        SubObj so;

    public:
        Obj() {};
        Obj(const Obj& o) {};
};

int main() {
    Obj();
    cout << "Program end" << endl;
}

When compiling with g++ I get the following output:

$ ./a.out
SubObj Destructor called
Program end

Based on my understanding, I expected the default destructor for Obj to not be auto-generated because I defined a copy operation for Obj. And thus, I expected that the SubObj member of Obj would not be destroyed because there is no destructor for Obj.

Thus, I'm wondering: are object members automatically destroyed even without a destructor? Or is a destructor somehow being auto-generated for this example?

Edit:

Later in the book (17.6.3.4), when referring to an example, Bjarne mentions:

We defined copy assignment, so we must also define the destructor. That destructor can be =default because all it needs to do is to ensure that the member pos is destyored, which is what would have been done anyway had the copy assignment not been defined.

Based on the answers so far, it sounds appears as though Bjarne may have just been wrong on this one.

johnnyodonnell
  • 1,838
  • 3
  • 16
  • 34

3 Answers3

5

That phrase from the book is poorly worded/wrong.

Of course a destructor is still generated if you provide a copy constructor. If it weren't, your program would not be able to be compiled.

If you provide your own destructor, a destructor is not generated. It doesn't need to be, and you can't have two.

Also, members are destroyed regardless of what your destructor does. A destructor allows you to do "extra" stuff, on top of the normal rules for object (and subobject) lifetime. There was never a risk that the SubObj member wouldn't be destroyed.

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

Bjarne's wording could have been better here. What

If the programmer declares a copy operation, a move operation, or a destructor for a class, no copy operation, move operation, or destructor is generated for that class.

Could more accurately be (but is still wrong, see the link below for the full rules)

If the programmer declares a copy operation, a move operation, or a destructor for a class, no copy operation, move operation, or destructor (respectively) is generated for that class.

meaning if you declare any of those special member functions, the compiler will not add it's own version. If you declare a copy constructor, it does not stop the destructor, only the copy constructor (and move in C++11+). Only defining a destructors stops the compiler from generating one. To see all of the rules see: What are all the member-functions created by compiler for a class? Does that happen all the time?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • 1
    That would not be correct either - declaring copy operation inhibits move as well, and vice versa. – SergeyA Apr 23 '19 at 14:55
  • 1
    @SergeyA That's part of the reason I linked to the conical. It's to complicated to sum up like bjarne did so I did the best I could. – NathanOliver Apr 23 '19 at 14:56
  • 1
    Sure, just the fact that it's hard to find a favorable reading for this quote :) – SergeyA Apr 23 '19 at 14:58
  • Worded much better than I could. I do want to add though that even though obj has no programmer defined destructor, so ( a SubObj) still needs to get destroyed. The default behavior of C++ is to call the destructor of all member variables, thus calling so's destructor anyway. –  Apr 23 '19 at 15:05
  • 1
    @Chipster Yes, `Obj` doesn't have a user provided one so the compiler provides one. That one just calls the destructor for all of it's non static members. You could actually write `~Obj() = delete;` to actually stop the object from being destroyed. I've never seen a use case for it but the language allows it. – NathanOliver Apr 23 '19 at 15:08
  • 1
    I added some info to my question. I think Bjarne may have just gotten this one wrong rather than just using poor wording. – johnnyodonnell Apr 23 '19 at 15:13
  • 2
    @johnnyodonnell Oh wow. Looks like he really is mistaken. I've found a copy and I'm trying to find the disconnect. – NathanOliver Apr 23 '19 at 15:20
  • 3
    @johnnyodonnell Okay, I've read the sections and I think I might know what he is doing. He is correct about the copy/move operations. I think he put the destructor in there because part of the core guidelines is to explicitly put in all of the special members if you put in any of them. It's not worded well and you can make a reasonable assumption that he claims the destructor must be put in because the standard says so. I'm going to chalk it up to him incorporating the GSL guidline. If you want you could write to him and link him to this Q and A. – NathanOliver Apr 23 '19 at 15:36
  • 1
    @NathanOliver That's really quite bad either way :( – Lightness Races in Orbit Apr 23 '19 at 22:37
2

I do not have this book to check what is actually written here, but either you are quoting it incorrectly, or it is inaccurate (the latter is hard to believe). The other option is that it is just poorly phrased and confusing.

The only time when compiler will not generate an implicit destructor is when it is explicit:

If no user-declared destructor is provided for a class type (struct, class, or union), the compiler will always declare a destructor as an inline public member of its class.

https://en.cppreference.com/w/cpp/language/destructor

SergeyA
  • 61,605
  • 5
  • 78
  • 137