什么时候允许编译器优化复制构造函数

When is the compiler allowed to optimize out the copy-constructor

今天我遇到了一些关于拷贝构造函数我不太了解的事情。

考虑下一个代码:

#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;
}

然后在给copy赋值origin的时候调用了copy构造函数,这就说得通了。但是,如果我将副本声明更改为

static some_class copy = some_class();

它没有被调用。即使我使用 create() 函数,它也不会调用复制构造函数。 但是,将其更改为

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

它确实调用了复制构造函数。 一些研究解释说,允许编译器优化复制构造函数,这听起来是件好事。直到复制构造函数是非默认的,那么它可能会或可能不会做一些显而易见的事情,对吗? 那么什么时候允许编译器优化复制构造函数?

从C++17开始,这种copy elision是有保证的,编译器不仅允许,而且要求省略复制(或移动)构造,即使复制(或移动)构造有明显的副作用。

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
    

您的代码片段与这两种情况完全匹配。

在 C++17 之前,不需要编译器,但允许省略复制(或移动)构造,即使 copy/move 构造函数具有可观察到的副作用。请注意,这是一种优化,即使发生复制省略,复制(或移动)构造函数仍然必须存在且可访问。

另一方面,call()不符合复制省略的任何条件;返回的是*this,既不是纯右值,也不是自动存期对象,需要复制构造,不能省略。