Copy elision

参考

C++17 prvalue semantics (“guaranteed copy elision”)

返回值(或函数参数)的位置,如果表达式是纯右值,且返回值(或函数参数)需要的也是同一类型的纯右值,那么标准就要求省略复制和移动。不过,为了让语义检查通过,要构造对象的析构函数必须在此处可以访问,尽管在完成优化之后并不会用到析构函数。

T f()
{
    return U(); // constructs a temporary of type U,
                // then initializes the returned T from the temporary
}
T g()
{
    return T(); // constructs the returned T directly; no move
}
T x = T(T(f())); // x is initialized by the result of f() directly; no move

The C++17 core language specification of prvalues and temporaries is fundamentally different from that of earlier C++ revisions: there is no longer a temporary to copy/move from. Another way to describe C++17 mechanics is “unmaterialized value passing” or “deferred temporary materialization”: prvalues are returned and used without ever materializing a temporary.

非强制的 copy/move elision

编译器一些场景可以对 copy/move 进行省略,但不是强制的(实测 gcc 和 clang 在默认优化等级下就会使用这项优化,而 MSVC 则是在 /O2 才会启用这项优化)。当这项优化发生时,复制 / 移动构造函数必须是可用的,尽管不会被实际执行。

Note

MSVC 编译器的文档:

This option (/Zc:nrvo) is off by default, but is set automatically when you compile using the /O2 option, the /permissive- option, or /std:c++20 or later.

其中 copy elision 是在 C++11 之前就允许的优化,而 move elision 是在 C++11 之后加入的。这里看原网站的分块标记会更加清楚一些。

这些场景包括:

  • 函数中非 volatile 且是自动存储期(automatic storage duration)对象的 id 表达式,且不是函数参数、catch 参数,且和返回类型的除去 cv 限定符之后类型相同,就能发生拷贝 / 移动省略。这个情况也被叫做 NRVO(named return value optimization)。
  • C++11 起、C++17 之前,return 语句上的无名临时对象,且对象类型和返回类型在忽略 cv 限定符后相同。这被称为 URVO(unnamed return value optimization)。在 C++17 之后,这一项成为强制要求,也不再被归类到 copy/move elision 中来。
  • C++11 起,throw 的表达式,非 volatile、自动存储器、非函数参数、非 catch 参数、作用域不超过最内层的 try-catch 块(如果有),也可以发生拷贝 / 移动省略。
  • C++11 起,catch 子句的实参在一定条件下可以省略拷贝(原因略去,可以去看原网页)。
  • C++20 起,可以消除将形参向协程状态内的复制 / 移动,只要除了对构造函数和析构函数的忽略外不会改变程序的其他行为即可。比如在暂停点后始终不使用形参,或者整个协程状态本就始终不在堆上分配时,可以应用这项优化。

注意

拷贝省略可能会改变程序的行为。所以依赖拷贝 / 移动的副作用工作的程序是不可移植的。

Copy elision is the only allowed form of optimization(until C++14) one of the two allowed forms of optimization, alongside allocation elision and extension,(since C++14) that can change observable side-effects.

和返回值优化的关系

我的理解:隐式移动和拷贝省略都能用于返回值优化,也能用于参数等位置,是函数或过程之间传递对象的优化技术;返回值优化从字面意思上来看是返回值相关的优化,可以通过多种技术手段来实现。因此,它们属于不同的分类范畴,并不是互斥的。