我有以下内容:
#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替换第二个副本)
答案 0 :(得分:1)
您的副本构造函数具有明显的副作用(输出)。 C ++优化的(几乎)黄金法则是视情况规则:不得更改可观察到的副作用。但是,此规则的两个例外之一是省略了某些复制操作,在此指定:
http://eel.is/c++draft/class.copy.elision
当满足某些条件时,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用,也允许实现忽略类对象的复制/移动构造。
但这仅在非常特殊的情况下允许:
在以下情况下允许使用此复制/移动操作的省略,称为复制删除(可以合并以消除多个副本):
- 在具有类返回类型的函数中的return语句中,当表达式是非易失性 automatic 对象[...]
的名称时
您的变量没有自动存储期限(即它不是本地变量)。
- 在throw表达式中,[...]
我们不是抛出表达式。
- 在一个协程[...]
我们不在协程中。
- 当异常处理程序的异常声明([except])声明与异常对象([except.throw])相同类型的对象(cv资格除外)[...]
也不是这样。
这里既不涉及从函数返回全局变量,也不涉及使用构造函数参数初始化成员变量,因此,对于任何编译器来说,在您显示的代码中删除这些副本都是非法的。
也就是说,如果编译器可以证明没有副作用(在大多数编译器中,它包括系统调用和内存分配),那么当然可以自由地按仿照规则删除副本,就像其他所有优化一样。