Copy elision
当满足特定条件时,即使所选构造函数和/或对象析构函数具有副作用,也可以省略从同类型源对象(忽略cv限定符)创建类对象的过程。这种对象创建的省略被称为 复制消除 。
目录 |
说明
在以下情况下允许进行复制消除(这些情况可以组合使用以消除多次复制):
- 在具有类返回类型的函数中的 return 语句 中,当操作数是具有 自动存储期 的非易失性对象(除函数参数或 异常处理程序 参数外)的名称 obj 时,可以通过将 obj 直接构造到函数调用的结果对象中来省略结果对象的 复制初始化 。这种复制消除的变体称为 命名返回值优化 (NRVO)。
|
(C++17 前) |
| (C++11 起) | |
|
(C++20 起) |
当发生复制消除时,编译器将被省略初始化的源对象和目标对象视为引用同一对象的两种不同方式。
|
在不进行优化的情况下,析构发生在两个对象原本会被销毁的时间中较晚的时刻。 |
(until C++11) |
|
若所选构造函数的首个参数是该对象类型的右值引用,则该对象的析构发生在目标对象原本会被销毁的时刻。否则,析构发生在两个对象原本会被销毁的时间中较晚的时刻。 |
(since C++11) |
纯右值语义("保证的拷贝消除")自 C++17 起,纯右值在需要时才被具体化,并直接在其最终目标的存储空间中构造。这意味着即使语言语法在视觉上暗示了拷贝/移动(例如 拷贝初始化 ),也不会执行拷贝/移动操作——这意味着该类型完全不需要具有可访问的拷贝/移动构造函数。示例包括: 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
struct C { /* ... */ }; C f(); struct D; D g(); struct D : C { D() : C(f()) {} // no elision when initializing a base class subobject D(int) : D(g()) {} // no elision because the D object being initialized might // be a base-class subobject of some other class }; 注意:此规则并非指定一种优化,标准也未正式将其描述为"拷贝消除"(因为没有内容被消除)。相反,C++17 核心语言对 纯右值 和 临时对象 的规范与早期 C++ 版本有根本区别:不再存在用于拷贝/移动的临时对象。描述 C++17 机制的另一种方式是"非具体化值传递"或"延迟临时对象具体化":纯右值在从未具体化临时对象的情况下被返回和使用。 |
(since C++17) |
注释
复制消除是 唯一允许的优化形式 (C++14前) 两种允许的优化形式之一,与 分配消除与扩展 并列, (C++14起) 可以改变可观测副作用的优化手段。由于某些编译器不会在所有允许的情况下执行复制消除(例如在调试模式下),依赖复制/移动构造函数和析构函数副作用的程序不具备可移植性。
|
在 return 语句或 throw 表达式中,若编译器无法执行复制消除但满足复制消除的条件,或除源对象为函数形参外本应满足条件时, 编译器将尝试使用移动构造函数,即使源操作数由左值指定 (C++23 前) 源操作数将被视为右值 (C++23 起) ;详见 return 语句 。 struct A { void* p; constexpr A() : p(this) {} A(const A&); // Disable trivial copyability }; constexpr A a; // OK: a.p points to a constexpr A f() { A x; return x; } constexpr A b = f(); // error: b.p would be dangling and point to the x inside f constexpr A c = A(); // (until C++17) error: c.p would be dangling and point to a temporary // (since C++17) OK: c.p points to c; no temporary is involved |
(since C++11) |
| 功能测试宏 | 值 | 标准 | 功能 |
|---|---|---|---|
__cpp_guaranteed_copy_elision
|
201606L
|
(C++17) | 通过简化的 值类别 实现保证的复制消除 |
示例
#include <iostream> struct Noisy { Noisy() { std::cout << "constructed at " << this << '\n'; } Noisy(const Noisy&) { std::cout << "copy-constructed\n"; } Noisy(Noisy&&) { std::cout << "move-constructed\n"; } ~Noisy() { std::cout << "destructed at " << this << '\n'; } }; Noisy f() { Noisy v = Noisy(); // (C++17前) 从临时对象初始化v时的复制消除; // 可能调用移动构造函数 // (C++17起) "保证复制消除" return v; // 从v到结果对象的复制消除("NRVO"); // 可能调用移动构造函数 } void g(Noisy arg) { std::cout << "&arg = " << &arg << '\n'; } int main() { Noisy v = f(); // (C++17前) 从f()返回值初始化v时的复制消除 // (C++17起) "保证复制消除" std::cout << "&v = " << &v << '\n'; g(f()); // (C++17前) 从f()返回值初始化arg时的复制消除 // (C++17起) "保证复制消除" }
可能的输出:
constructed at 0x7fffd635fd4e &v = 0x7fffd635fd4e constructed at 0x7fffd635fd4f &arg = 0x7fffd635fd4f destructed at 0x7fffd635fd4f destructed at 0x7fffd635fd4e
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 1967 | C++11 |
使用移动构造函数进行复制消除时,
仍考虑被移动对象的生存期 |
不予考虑 |
| CWG 2426 | C++17 | 返回纯右值时未要求调用析构函数 | 可能调用析构函数 |
| CWG 2930 | C++98 |
仅能消除复制/移动操作,但复制初始化
可能选择非复制/移动构造函数 |
消除相关复制初始化中
的所有对象构造 |