Why is automatic object's destructor called twice?

1.4k Views Asked by At

(The answer to my question involves copy constructors, but the copy takes place upon return from a function, not within a method call to another class. I actually saw the referenced possible duplicate, but did not infer from the copy made by vector::push_back that my function here also made a copy. Perhaps I should have.)

I am trying to understand the construction/destruction of automatic objects. I ran into some code that looked dubious to me, so I wrote my own version in an effort to understand it. In short, the original code included a function that returned an object that was local to the function (an automatic). That looked unsafe to me, so I wrote this program to explore it:

#include <stdio.h>

class Phantom
{
private:
    static int counter;
    int id;

public:
    Phantom()
    {
        ++counter;
        id = counter;
        printf("Phantom %d constructed.\n", id);
    };

    virtual ~Phantom()
    {
        printf("Phantom %d destructed.\n", id);
    };

    void speak()
    {
        printf("Phantom %d speaks.\n", id);
    };
};

int Phantom::counter = 0;

Phantom getPhantom()
{
    Phantom autoPhantom;

    return autoPhantom; // THIS CAN'T BE SAFE
}

int main()
{
    Phantom phantom;

    phantom = getPhantom();

    phantom.speak();

    return 0;
}

I get this output:

Phantom 1 constructed.
Phantom 2 constructed.
Phantom 2 destructed.
Phantom 2 destructed.
Phantom 2 speaks.

It's the fourth line in the output that confuses me.

Phantom 1 is constructed automatically when main is entered.

Phantom 2 is constructed automatically when getPhantom is entered.

Phantom 2 is destructed automatically when getPhantom is exited (which is why I believe returning it from getPhantom is unsafe).

But after that I'm confused. According to the debugger, getPhantom has returned before the fourth line of output appears. When Phantom's destructor is called the second time, the call stack is this:

main
~Phantom

In a managed language, I could see how this line:

phantom = getPhantom();

would destroy Phantom 1, but it wouldn't touch Phantom 2. And this is C++, not Java.

What causes the second call to Phantom 2's destructor?

6

There are 6 best solutions below

1
Resurrection On BEST ANSWER

You return a copy. Therefore the variable in getPhantom() is destroyed at the end of the scope and you are left with its copy that has also id 2. It is because on return it calls copy constructor (also default one) that does not increment the id.

2
Sam Varshavchik On

You are forgetting to properly account for:

  1. Copy constructors.

  2. Assignment operators.

In both of these cases you will wind up with more than one object having the same id, with both objects ending up printing the same id in their destructor. In the case of a copy constructor no message gets printed in the constructor, since you do not define your own copy constructor. In the case of an assignment operator, the id assigned in the constructor get overwritten by a duplicate id from another object. This is what happens here:

phantom = getPhantom();

As such, your accounting gets this wrong.

8
Jonathan Wakely On

Instead of questioning whether such simple code results in destroying an object that was never constructed, or destroying something twice, consider that it's far more likely the object was constructed and each object is only destroyed once, but you didn't track the constructions and destructions accurately.

Now think about other ways objects can be constructed in C++, and consider what happens if a copy constructor is used at any point. Then consider how you return a local object from a function, and whether the copy constructor gets used.

If you want to improve your test code print out the value of the this pointer in the destructor, and you'll see that your attempt to give each object an ID is flawed. You have multiple objects with different identities (i.e. addresses in memory) but the same "ID".

9
Jesper Juhl On

Phantom autoPhantom;

return autoPhantom; // THIS CAN'T BE SAFE

It's perfectly safe. The function is returning the object by value, that is, a copy will be made and returned (possibly elided by "return value optimization" (RVO)).

If the function had returned a reference or pointer to the local variable, then you would be right and it would be unsafe.

The reason for the "extra" destructor call is simply that the local variable is destroyed and later the copy that was returned is destroyed.

8
bolov On

I will comment on your concern that it's not safe to return an object with automatic storage:

Phantom getPhantom()
{
    Phantom autoPhantom;

    return autoPhantom; // THIS CAN'T BE SAFE
}

If that wouldn't be safe, then C++ would be pretty useless, don't you think? To see what I am talking about, just replace the type with... say int:

int getPhantom()
{
    int autoPhantom = 0;

    return autoPhantom; // How does this look to you now?
}

To be clear: it's perfectly safe, because you are returning the value (i.e. a copy of the object).

What is unsafe is to return a pointer or a reference to such an object:

int* getInt()
{
   int a = 0;
   return &a;
}
1
katagaeshi On

Add such code to your class:

Phantom& operator=(const Phantom& inPhantom)
{
    printf("Assigning.\n");
}

and you'll see that 2nd object is not destroyed twice. The explnanation is simplier. On assignment operation first object changes all it's fields values to the values of the second object, but it is not destroyed. And it is still object number one. Your updated example: http://cpp.sh/6b4lo