2
votes

Problem

The destructor gets called twice in the following code:

class Foo
{
public: 
    ~Foo()
    {
        std::cout << "Destructor called\n";
    }

    Foo& operator=(const Foo& other)
    {
        std::cout << "Assignment called\n";

        return *this;
    }
};

Foo foo()
{
    return Foo();
}

int main()
{
    foo();

    return 0;
}

Output:

Destructor called
Destructor called

I suspect this is due to some implicit call to an assignment operator or a copy constructor. I cannot tell if the copy constructor gets called as adding a constructor of any kind magically solves the problem (as explained further down), but at least the assignment operator does not get called.

And as mentioned, if I add a constructor, the problem goes away:

class Foo
{
public:
    Foo()
    {
        std::cout << "Constructor called\n";
    }

    Foo(const Foo& other)
    {
        std::cout << "Copy constructor called\n";
    }

    ~Foo()
    {
        std::cout << "Destructor called\n";
    }

    Foo& operator=(const Foo& other)
    {
        std::cout << "Assignment called\n";

        return *this;
    }
};

The output changes into:

Constructor called
Destructor called

The problem also goes away if I return a reference instead of an object (but results in a "returning address of local or temporary variable" warning):

Foo& foo()
{
    return Foo();
}

Question

Why is the destructor called twice, and why is the behavior different when using a default constructor or not? Is there a logical explanation or could it be a mistake by the compiler?

I'm using MSVC 2013 if it could make any difference.

3
You forgot to include the copy constructor. And to test the code in the Release build so that the Return Value Optimization can avoid the extra copy. - Hans Passant
Assignment operator should return non-const reference. - Neil Kirk
The number of destructor calls depends on the number of constructor calls, which depends on the optimizations, i.e. the compiler and options. - Cheers and hth. - Alf
How do you know that no copy constructor is called in the first example? - milleniumbug

3 Answers

1
votes
Foo foo()
{
    return Foo(); // temporary is created and copied into the return value. 
                  // The temporary gets destroyed at the end of the copy constructor.
}

int main()
{
    foo(); // discarded-value expression; the return value gets destroyed immediately.
}

The compiler is allowed to apply copy-elision - in this case, NRVO (§12.8/31):

— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

Note that this works regardless of whether the copy constructor has any side effects or not. This is an "optimization" that can legally change the observable behavior of the program without disobeying the standard.

Since the compiler is not obliged to optimize anything, results may differ from code to code (and optimization level).

1
votes

The return statement copies your return value into the temporary object in scope of the calling function, that is, main. Then the temporary object Foo(), created in scope of foo, gets destructed, and then the temporary in scope of main gets destructed. The compiler is allowed, but not obligated, to optimize this away.

-1
votes

In the function foo() your object gets created. It returns a copy of that object. When the function ends, the first object (created in the function) goes out of scope, invoking the destructor.

Next, you main() gets a whole new object. And when it ends, it invokes destructor of this new object.

For the following:

Foo& foo()
{
   return Foo();
}

..You return reference to a local variable which gives a worthless return and main function doesn't get an object at all, no duplicate destructor is called.