复制消除

来自cppreference.com
< cpp‎ | language
 
 
C++ 语言
 

忽略复制和移动 (C++11 起)构造函数,导致零复制的按值传递语义。

目录

[编辑] 解释

在下列环境下,要求编译器省略类对象的复制及移动构造,即使复制/移动构造函数和析构函数拥有可观察的副效应。它们不必存在或可访问,因为语言规则甚至在概念上确保不发生复制/移动:

  • 在初始化中,若初始化器表达式为纯右值,且源类型的无 cv 限定版本与目标的类型相同,则初始化器表达式被用于初始化目标对象:
T x = T(T(T())); // 仅调用一次T的默认构造函数,以初始化x
  • 在函数调用中,若 return 语句的运算数是纯右值,且函数返回类型与该纯右值的类型相同。
T f() { return T{}; }
T x = f();         // 仅调用一次T的默认构造函数,以初始化 x
T* p = new T(f()); // 仅调用一次T的默认构造函数,以初始化 *p

注意:上述规则不指定一个优化: 纯右值临时量在 C++17 核心语言规范和较早 C++ 标准版本有基础区别:不再有复制/移动来源的临时量。

(C++17 起)

在下列环境中,容许但不要求编译器忽略类对象的复制和移动 (C++11 起)构造,即使复制/移动 (C++11 起)构造函数和析构函数拥有可见的副效应。这是一种优化:即使它发生,且不调用复制/移动构造函数,复制/移动构造函数也必须存在且可访问(如同优化完全不发生),否则程序为病态。

  • 若函数以值返回类型,且 return 语句的表达式是拥有自动存储期的非 volatile 对象之名,且该对象不是函数参数或 catch 子句的参数,且拥有与函数返回类型相同的类型(忽略顶层 cv 限定),则复制/移动 (C++11 起)被消除。当创建局部对象时,它直接被构造于函数返回值本来要移动或 (C++11 起)复制到的存储位置。此复制消除的变体称为 NRVO ,“具名返回值优化( named return value optimization )”。
  • 当未被绑定到任何引用的无名临时量,会被复制或移动 (C++11 起)到同一类型的对象时(忽略顶层 cv 限定),则复制/移动 (C++11 起)被忽略。构造临时量时,直接把它构造到本来要复制或移动 (C++11 起)到的存储位置。该无名临时量是返回语句的参数时,此复制消除的变体被称为 RVO ,“返回值优化( return value optimization )”。
(C++17 前)

此优化是强制的;见上述。

(C++17 起)
  • throw 表达式中,若运算数是拥有自动存储期的非 volatile 对象之名,且该对象不是函数参数或 catch 子句的参数,且其作用域不延伸超过最内层 try 块(若有 try 块),则复制/移动被忽略。当构造局部对象时,它直接被构造在该异常对象本来要被移动或复制到的存储位置。
  • 在处理异常时,若 catch 子句的参数类型(忽略顶层 cv 限定)与所抛出异常对象的类型相同,则复制被忽略,且 catch 子句体直接访问该异常对象,宛如按引用捕获它。在此种复制消除会改变跳过 catch 子句的复制构造函数及析构函数以外的程序可观察行为的情况下,禁止此复制消除。
(C++11 起)

在复制消除发生时,实现会将被忽略的复制/移动 (C++11 起)操作的源与目标简单地当做两种引用同一对象的方式,而该对象的析构发生与无优化时两对象析构时机的较迟者(除非所选构造函数的参数是对象类型的右值引用,则析构在目标本应销毁时发生) (C++17 起)

多次复制消除能用以消除多次复制。

struct A {
    void *p;
    constexpr A(): p(this) {}
};
 
constexpr A g() {
  A a;
  return a;
}
 
constexpr A a;        // a.p指向a
constexpr A b = g();  // b.p指向b(保证NRVO)
 
void g() {
  A c = g();          // c.p可能指向c或转瞬即逝的临时量
}
(C++14 起)

[编辑] 注意

复制消除是能改变可观察副效应的,仅有的得到允许的优化形式 (C++14 前)唯二允许的优化形式之一,另一种是分配消除及拓展 (C++14 起)。因为一些编译器不在每个允许的位置进行复制忽略(例如,在 debug 模式),依赖复制/移动 (C++11 起)构造函数及析构函数副作用的程序是不可移植的。

在 return 语句或 throw 表达式中,若编译器无法进行复制消除,但满足或可能满足复制消除的条件,除非源是函数参数,否则编译器将强制使用移动构造函数,即使该对象为左值所指代;细节见 return 语句

(C++11 起)

[编辑] 示例

#include <iostream>
#include <vector>
 
struct Noisy
{
    Noisy() { std::cout << "constructed\n"; }
    Noisy(const Noisy&) { std::cout << "copy-constructed\n"; }
    Noisy(Noisy&&) { std::cout << "move-constructed\n"; }
    ~Noisy() { std::cout << "destructed\n"; }
};
 
std::vector<Noisy> f()
{
    std::vector<Noisy> v = std::vector<Noisy>(3); // 从临时量 (C++17 前)
                                                  // 从纯右值 (C++17 起)
                                                  // 初始化 v 时复制消除
    return v; // 从 v 到结果对象的 NRVO ( C++17 中不保证)
}             // 若禁用优化,则调用移动构造函数
 
void g(std::vector<Noisy> arg)
{
    std::cout << "arg.size() = " << arg.size() << '\n';
}
 
int main()
{
    std::vector<Noisy> v = f(); // 从临时量 (C++17 前)
                                // 从纯右值 (C++17 起)
                                // 初始化 v 时复制消除
    g(f());                     // 从 f() 返回的临时量 (C++17 前)
                                // 从纯右值 f() (C++17 起)
                                // 初始化 g() 的形参时复制消除
}

可能的输出:

constructed
constructed
constructed
constructed
constructed
constructed
arg.size() = 3
destructed
destructed
destructed
destructed
destructed
destructed


[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 2022 C++14 复制消除在常量表达式中可选 强制复制消除

[编辑] 参阅