C ++优化:避免复制操作

时间:2019-09-26 14:58:04

标签: c++ compiler-optimization move-semantics copy-elision

我有以下内容:

#include <iostream>
#include <utility>

class T {
public:
    T() {std::cout << "Default constructor called" << std::endl;}
    T(const T&) {std::cout << "Copy constructor called" << std::endl;}
};

static T s_t;

T foo() {
    return s_t;
}

class A {
public:
    A() = delete;
    A(T t) : _t(t) {}
private:
    T _t;
};

int main() {

    std::cout << "Starting main" << std::endl;
    A a(foo());
    return 0;

}

当我用以下代码编译它时: g++ -std=c++17 -O3 test.cpp并运行它,我得到以下输出

Default constructor called
Starting main
Copy constructor called
Copy constructor called

我的问题是:由于A的构造函数正在使用类型T的r值对象,该对象仅用于初始化_t,因此编译器有可能避免第二个复制操作,然后将s_t直接复制到_t吗?

(我了解通过为T实现move构造函数,我有可能用move替换第二个副本)

1 个答案:

答案 0 :(得分:1)

您的副本构造函数具有明显的副作用(输出)。 C ++优化的(几乎)黄金法则是视情况规则:不得更改可观察到的副作用。但是,此规则的两个例外之一是省略了某些复制操作,在此指定:

http://eel.is/c++draft/class.copy.elision

  

当满足某些条件时,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用,也允许实现忽略类对象的复制/移动构造。

但这仅在非常特殊的情况下允许:

  

在以下情况下允许使用此复制/移动操作的省略,称为复制删除(可以合并以消除多个副本):

     
      
  • 在具有类返回类型的函数中的return语句中,当表达式是非易失性 automatic 对象[...]
  • 的名称时   

您的变量没有自动存储期限(即它不是本地变量)。

  
      
  • 在throw表达式中,[...]
  •   

我们不是抛出表达式。

  
      
  • 在一个协程[...]
  •   

我们不在协程中。

  
      
  • 当异常处理程序的异常声明([except])声明与异常对象([except.throw])相同类型的对象(cv资格除外)[...]
  •   

也不是这样。

这里既不涉及从函数返回全局变量,也不涉及使用构造函数参数初始化成员变量,因此,对于任何编译器来说,在您显示的代码中删除这些副本都是非法的。

也就是说,如果编译器可以证明没有副作用(在大多数编译器中,它包括系统调用和内存分配),那么当然可以自由地按仿照规则删除副本,就像其他所有优化一样。