Destructors on classes returned by functions

Viewed 148

I have the following code:

#include <stdio.h>

class Foo {
    public:
    int a;
    ~Foo() { printf("Goodbye %d\n", a); }
};

Foo newObj() {
    Foo obj;
    return obj;
}

int main() {
    Foo bar = newObj();
    bar.a = 5;
    bar = newObj();
}

When I compile with g++ and run it, I get:

Goodbye 32765
Goodbye 32765

The number printed seems to be random.

I have two questions:

  1. Why is the destructor called twice?
  2. Why isn't 5 printed the first time?

I'm coming from a C background, hence the printf, and I'm having trouble understanding destructors, when they are called and how a class should be returned from a function.

3 Answers

Let's see what happens in your main function :

int main() {
    Foo bar = newObj();

Here we just instantiate a Foo and initialize it with the return value of newObj(). No destructor is called here because of copy elision: to sum up very quickly, instead of copying/moving obj into bar and then destructing obj, obj is directly constructed in bar's storage.

    bar.a = 5;

Nothing to say here. We just change bar.a's value to 5.

    bar = newObj();

Here bar is copy-assigned1 the returned value of newObj(), then the temporary object created by this function call is destructed2, this is the first Goodbye. At this point bar.a is no longer 5 but whatever was in the temporary object's a.

}

End of main(), local variables are destructed, including bar, this is the second Goodbye, which does not print 5 because of previous assignment.


1 No move assignment happens here because of the user-defined destructor, no move assignment operator is implicitly declared.
2 As mentioned by YSC in the comments, note that this destructor call has undefined behavior, because it is accessing a which is uninitialized at this point. The assignment of bar with the temporary object, and particularly the assignment of a as part of it, also has undefined behavior for the same reasons.

1) It's simple, there are two Foo objects in your code (in main and in newObj) so two destructor calls. Actually this is the minimum number of destructor calls you would see, the compiler might create an unnamed temporary object for the return value, and if it had done that you would see three destructor calls. The rules on return value optimization have changed over the history of C++ so you may or may not see this behaviour.

2) Because the value of Foo::a is never 5 when the destructor is called, its never 5 in newObj, and though it was 5 in mainit isn't by the time you get to the end of main (which is when the destructor is called).

I'm guessing your misunderstanding is that you think that the assignment statement bar = newObj(); should call the destructor, but that's not the case. During assignment an object gets overwritten, it doesn't get destroyed.

I think one of the main confusions here is object identity.

bar is always the same object. When you assign a different object to bar, you don't destroy the first one - you call operator=(const& Foo) (the copy assignment operator). It is one of the five special member functions that can be auto-generated by the compiler (which it is in this case) and just overwrites bar.a with whatever is in newObj().a. Provide your own operator= to see that/when this happens (and to confirm that a is indeed 5 before this happens).

bar's destructor is only called once - when bar goes out of scope at the end of the function. There is only one other destructor call - for the temporary returned by the second newObj(). The first temporary from newObj() is elided (the language allows it in this exact case because there is never really a point to creating and immediately destroying it) and initializes bar directly with the return value of newObj().

Related