c++copy-constructorpass-by-valuereturn-by-value

Why is copy constructor not called


Here is a simple class header file and a main program. In the main program, I thought that the copy constructor is called in exactly three situations: initialization(explicit copy), pass by value for function arguments, and return by value for functions. However it seems like it is not being called for one of them, I think either (3) or (4) as numbered in the comments. For which numbers (1) - (4) does it get called? Thanks.

X.h:

#include <iostream>

class X
{
public:
    X() {std::cout << "default constructor \n";}
    X(const X& x) { std::cout << "copy constructor \n";}
};

Main:

#include "X.h"

X returnX(X b)  // (1) pass by value - call copy constructor?
{
    X c = b;  // (2) explicit copy - call copy constructor?
    return b;  // (3) return by value - call copy constructor?
}

int main()
{
    X a; // calls default constructor

    std::cout << "calling returnX \n\n";
    X d = returnX(a);  // (4) explicit copy - call copy constructor?
    std::cout << "back in main \n";
}

Output:

default constructor
calling returnX

copy constructor
copy constructor
copy constructor 
back in main

Solution

  • Since copying happens frequently in C++ and since it may be expensive the compiler is allowed to elide certain copy (and move) constructions. This copy elision is allowed even if the elided constructor and/or the destructor has side effects like the output in your program (that is, it isn't really an optimization as the behavior with and without copy elision is different).

    There are four basic places where copy elision can be applied according to 12.8 [class.copy] paragraph 31:

    1. In a return statement when directly returning a local variable which has the same type as the return type of the function.
    2. In a throw statement a copy of an automatic variable within the innermost try-block can be elided when the object is thrown.
    3. When a temporary object wasn't bound to a reference copying it can be elided.
    4. When a catch clause catches the object by value and with the same type as the object in the throw statement the copy can be elided.

    The exact rules are slightly more complicated but I think this is the gist of it. Given that the rules for copy elision are fairly strict it is easy to suppress copy elision: the easiest way is to use an identity() function:

    template <typename T>
    T const& identity(T const& object) {
        return object;
    }
    ...
    X d = identity(returnX(a));
    

    (this version also inhibits move construction, though; deducing the type using a T&& and returning it appropriately should make move construction possible but I'm not quite sure what the return type and the return statement should be).