Search code examples
c++copy-constructortemporary-objectsreturn-value-optimizationcopy-elision

Copy constructor not called when initializing an object with return value of a function


Consider the following code:

#include <iostream>

using namespace std;

class A
{
    public:
        int a;
        A(): a(5)
        {
           cout << "Constructor\n";
        }
        A(const A &b)
        {
            a = b.a;
            cout << "Copy Constructor\n";
        }
        A fun(A a)
        {
            return a;
        }
};

int main()
{
    A a, c;
    A b = a.fun(c);
    return 0;
}

The output of the above code with g++ file.cpp is:

Constructor
Constructor
Copy Constructor
Copy Constructor

The output of the above code with g++ -fno-elide-constructors file.cpp is:

Constructor
Constructor
Copy Constructor
Copy Constructor
Copy Constructor

I know Return Value Optimization. My question is which call to copy constructor is elided(temporary object during returning or returned object being copied to b)?

If the elided copy constructor is the one used for creating b, then how is b created at all (because there is no constructor call in this case also)?

If I replace the line A b = a.fun(c); with a.fun(c) and compile using the first method or even the second method, then also the copy constructor is being called 2 times . So, if in the case explained in the previous paragraph, the temporary object's copy constructor is elided, then why isn't it elided in this case?


Solution

  • #include <iostream>
    
    using namespace std;
    
    class A
    {
    public:
        int a;
        A(): a(5)
        {
            cout << "Constructing: " << (void *)this << std::endl;
        }
        A(const A &b)
        {
            a = b.a;
            cout << "Copy Constructor: " << (void *)this << " from " << (void *)&b << std::endl;
        }
        A fun(A a)
        {
            return a;
        }
    };
    
    int main()
    {
    
        A a, c;
        A b = a.fun(c);
    
        std::cout << "a:" << (void *)&a << std::endl <<
                  "b:" << (void *)&b << std::endl <<
                  "c:" << (void *)&c << std::endl;
        return 0;
    }
    

    Yields:

    Constructing: 0x7fffbb377220
    Constructing: 0x7fffbb377210
    Copy Constructor: 0x7fffbb377230 from 0x7fffbb377210
    Copy Constructor: 0x7fffbb377200 from 0x7fffbb377230
    a:0x7fffbb377220
    b:0x7fffbb377200
    c:0x7fffbb377210
    

    So it constructs a, constructs c, copies c to an intermediate (argument a of the function), and then copies the intermediate directly into b, skipping the typical copying of a to a return intermediate. This is even better demonstrated if you pass by value (change to A fun(const A& a):

    Constructing: 0x7fff8e9642b0
    Constructing: 0x7fff8e9642a0
    Copy Constructor: 0x7fff8e964290 from 0x7fff8e9642a0
    a:0x7fff8e9642b0
    b:0x7fff8e964290
    c:0x7fff8e9642a0
    

    a is constructed, c is constructed, c is copied directly to b, despite b not being passed to fun!