编译器何时可以优化掉复制构造函数?

6
今天我遇到了一个关于拷贝构造函数的问题,不太理解。
考虑以下代码:
#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();

它确实会调用复制构造函数。 一些研究解释说,编译器允许优化掉复制构造函数,这听起来是一件好事。但是,如果复制构造函数不是默认的,那么它可能会或可能不会做一些明显的事情,对吗? 那么,什么时候编译器可以优化掉复制构造函数呢?


静态的some_class copy = some_class(); - 这将调用默认构造函数。 - Rhathin
在编写问题的代码时,有一个小技巧:提供能够展示问题的代码。在这里,您提供了按照您期望的方式工作的代码,然后给出了如何修改它以显示问题的说明。这是相反的。让人们可以轻松地复制和粘贴代码。 - Pete Becker
一个相关的问题:https://dev59.com/v2cs5IYBdhLWcg3wTyRl。还要注意,在`static some_class copy = origin;中,copy`并没有被赋值,而是进行了_复制构造_(这是两个不同的概念)。 - Daniel Langr
1个回答

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

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之前,编译器不是必须的,但允许省略复制(或移动)构造函数,即使复制/移动构造函数具有可观察的副作用。请注意,这是一种优化,即使发生了复制省略,复制(或移动)构造函数仍必须存在且可访问。
另一方面,call()不符合任何复制省略条件;它返回*this,既不是prvalue也不是具有自动存储期限的对象,需要进行复制构造并且不能省略。

即使在 C++17 之前,即使存在副作用,也允许删除复制构造函数(只是不强制要求)。 - Martin York

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接