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

use*_*926 4 c++ copy-constructor language-lawyer copy-elision

今天,我遇到了一些我对复制构造函数不太了解的内容。

考虑下面的代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

然后在将原点分配给副本时调用副本构造函数,这很有意义。但是,如果我将复制声明更改为

static some_class copy = some_class();
Run Code Online (Sandbox Code Playgroud)

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

static some_class copy = some_class().call();
Run Code Online (Sandbox Code Playgroud)

它确实调用了复制构造函数。一些研究解释说,允许编译器优化复制构造函数,这听起来不错。直到copy-constructor是非默认的,否则它可能会或可能不会做明显的事情,对不对?那么什么时候编译器可以优化复制构造函数呢?

son*_*yao 5

由于C ++ 17,这种复制省略保证,编译器不只是允许的,但省略复制(或移动)建设所需,即使复制(或移动)构造具有可观察到的副作用。

在以下情况下,即使复制/移动构造函数和析构函数具有明显的副作用,也要求编译器忽略类对象的复制和移动构造。这些对象直接构造到存储中,否则会将它们复制/移动到其中。复制/移动构造函数不需要存在或不可访问,因为语言规则可确保不会进行复制/移动操作,即使在概念上也是如此:

  • 在变量的初始化中,当初始化器表达式是与变量类型相同的类类型(忽略cv限定)的prvalue时:

    T x = T(T(T())); // only one call to default constructor of T, to initialize x
    
    Run Code Online (Sandbox Code Playgroud)
  • 在return语句中,当操作数是与函数返回类型相同的类类型(忽略cv限定)的prvalue时:

    T f() {
        return T();
    }
    
    f(); // only one call to default constructor of T
    
    Run Code Online (Sandbox Code Playgroud)

您的代码段完全符合这两种情况。

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

另一方面,call()复制省略的任何条件都不匹配;它返回*this,既不是prvalue也不是具有自动存储期限的对象,它是必需的复制结构,不能省略。