Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is the compiler allowed to optimize out the copy-constructor [duplicate]

Today I encountered something I don't really understand about copy constructor.

Consider the next code:

#include <iostream>
using namespace std;

class some_class {
  public:
    some_class() {

    }

    some_class(const some_class&) {
      cout << "copy!" << endl;
    }

    some_class call() {
      cout << "is called" << endl;
      return *this;  // <-- should call the copy constructor
    }
};

some_class create() {
  return some_class();
}

static some_class origin;
static some_class copy = origin; // <-- should call the copy constructor

int main(void)
{
  return 0;
}

then the copy constructor is called when assigning origin to copy, which makes sense. However, if I change the declaration of copy into

static some_class copy = some_class();

it isn't called. Even when I am using the create() function, it does not call the copy constructor. However, when changing it to

static some_class copy = some_class().call();

it does call the copy constructor. Some research explained that the compiler is allowed to optimize the copy-constructor out, which sound like a good thing. Until the copy-constructor is non-default, as then it might, or might not do something obvious, right? So when is it allowed for the compiler to optimize out the copy-constructor?

like image 392
user2546926 Avatar asked Sep 21 '18 14:09

user2546926


1 Answers

Since C++17, this kind of copy elision is guaranteed, the compiler is not just allowed, but required to omit the copy (or move) construction, even if the copy (or move) constructor has the observable side-effects.

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:

  • In the initialization of a variable, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:

    T x = T(T(T())); // only one call to default constructor of T, to initialize x
    
  • In a return statement, when the operand is a prvalue of the same class type (ignoring cv-qualification) as the function return type:

    T f() {
        return T();
    }
    
    f(); // only one call to default constructor of T
    

Your code snippet matches these two cases exactly.

Before C++17, the compiler is not required, but allowed to omit the copy (or move) construction, even if the copy/move constructor has observable side-effects. Note that it's an optimization, even copy elision takes place the copy (or move) constructor still must be present and accessible.

On the other hand, call() doesn't match any conditions of copy elision; it's returning *this, which is neither a prvalue nor a object with automatic storage duration, copy construction is required and can't be omitted.

like image 72
songyuanyao Avatar answered Sep 20 '22 14:09

songyuanyao