允许编译器何时优化复制构造函数

时间:2018-09-21 14:59:37

标签: c++ language-lawyer copy-constructor 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;
}

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

static some_class copy = some_class();

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

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

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

1 个答案:

答案 0 :(得分:4)

自C ++ 17起,保证了这种copy elision,不仅允许编译器使用,而且要求编译器省略复制(或移动)构造,即使复制(或移动)构造函数具有可观察到的副作用。

  

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

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

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

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

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

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

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