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

1

6

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?

Hemihedral answered 21/9, 2018 at 14:59 Comment(3)
static some_class copy = some_class(); - it will call default constructorAalto
A tip on writing code in questions: provide code that shows the problem. Here you've provided code that works the way you expect, then you give instructions on how to modify it to show the problem. That's backwards. Make it simple for people to copy and paste the code.Prologue
A related question: https://mcmap.net/q/15919/-what-are-copy-elision-and-return-value-optimization/580083. Also note that in static some_class copy = origin;, copy is not assigned to, it is copy-constructed (which are two different things).Vibratory
C
6

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.

Campo answered 21/9, 2018 at 15:4 Comment(1)
Even before C++17 it was allowed to remove the copy constructor even if there were side affects (it just was not required).Bluepoint

© 2022 - 2024 — McMap. All rights reserved.