0

In order to understand copy elision I wrote a small example

#include "iostream"

using namespace std;

struct Foo
{
    Foo() { cout << "constructor was called" << endl; }
    ~Foo() { cout << "destructor was called" << endl; }
};

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

int main()
{
    
    Foo bla = f();
    return 0;
}

output :

constructor was called
destructor was called
destructor was called

As I understand it, in the f() function the foo object is constructed directly in the memory space where bla is allocated so the constructor is called only once. I understand that when we exit the main() function, the bla object goes out of scope and the destructor is called. But why is the destructor called twice? Is the destructor of foo called when we exit the scope of the f() function even though the object is not destroyed and used by the caller?

roi_saumon
  • 489
  • 4
  • 13

1 Answers1

3

I also added logging of copy constructor in the code, to illustrate what happens.

#include "iostream"

using namespace std;

struct Foo
{
    Foo() { cout << "constructor was called" << endl; }
    Foo(const Foo&) {cout << "copy constructor was called" << endl;}
    ~Foo() { cout << "destructor was called" << endl; }
};

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

int main()
{
    
    Foo bla = f();
    return 0;
}

The output is

constructor was called
copy constructor was called
destructor was called
destructor was called

So we see that from two possible object construction both took place, and one used copy-constructor.

If we turn on optimizations, we get

constructor was called
destructor was called

Now the object is constructed once directly in memory space, and the destructor is called once. Here is a DEMO

Karen Baghdasaryan
  • 2,407
  • 6
  • 24
  • Thank you for the answer. I am still confused. Once the copy constructor is called, we are already into the scope of main() and so we have only the bla object left. Why is the destructor then called twice? (I am talking about the first output) – roi_saumon Apr 28 '23 at 14:00
  • In the first output, two objects are created. First is created via the no arg constructor, and the second is created via the copy constructor. Both objects have to be individually destroyed. If you modify the logging functions to print the values of `this`, you will see there are two separate objects being created and destroyed. – Dave S Apr 28 '23 at 14:05
  • @DaveS, but doesn't the destructor of the first created object has to be called before the constructor of the second created object? Indeed once we evaluate the expression f(), the first object is already out of scope so it should be destructed before the assignment no? – roi_saumon Apr 28 '23 at 14:10
  • No, when the copying takes place, the first object still exists as temporary. Once the copying ends, the destructor of the first object is called. If the destructor of the first object was called before the constructor of the second object, the second object would have nothing to copy from. – Karen Baghdasaryan Apr 28 '23 at 14:13
  • 1
    Oh, right, that makes sense. Thanks! Also I was confused because in MSVC, NRVO is applied by default and to remove it we need the `/Zc:nrvo-` compiler option. This is true for MSVC 2022 but in MSVC 2019 nrvo is turned off by default – roi_saumon Apr 28 '23 at 14:42