复制省略
当满足特定条件时,可以省略从相同类型的源对象(忽略 cv 限定)创建类对象的操作,即使所选的构造函数和/或对象的析构函数具有副作用。这种省略对象创建的操作称为复制省略。
目录 |
[编辑] 解释
在以下情况下允许复制省略(可以组合使用以消除多个副本)
- 在具有类返回类型的函数中的 return 语句 中,当操作数是具有自动存储期(函数形参或 处理程序 形参除外)的非易失性对象 obj 的名称时,可以通过将 obj 直接构造到函数调用的结果对象中来省略结果对象的复制初始化。这种复制省略的变体称为具名返回值优化 (NRVO)。
|
(C++17 前) |
(C++11 起) | |
|
(C++20 起) |
当发生复制省略时,实现将省略初始化的源和目标视为简单地引用同一对象的两种不同方式。
销毁发生在没有优化的情况下两个对象将被销毁的较晚时间。 |
(C++11 前) |
如果所选构造函数的第一个形参是对对象类型的右值引用,则该对象的销毁发生在目标将被销毁时。否则,销毁发生在没有优化的情况下两个对象将被销毁的较晚时间。 |
(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 机制的另一种方法是“未物化的值传递”或“延迟临时量物化”:纯右值在不物化临时量的情况下被返回和使用。 |
(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 |
(C++11 起) |
特性测试宏 | 值 | Std | 特性 |
---|---|---|---|
__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(); // (until C++17) copy elision initializing v from a temporary; // the move constructor may be called // (since C++17) "guaranteed copy elision" return v; // copy elision ("NRVO") from v to the result object; // the move constructor may be called } void g(Noisy arg) { std::cout << "&arg = " << &arg << '\n'; } int main() { Noisy v = f(); // (until C++17) copy elision initializing v from the result of f() // (since C++17) "guaranteed copy elision" std::cout << "&v = " << &v << '\n'; g(f()); // (until C++17) copy elision initializing arg from the result of f() // (since C++17) "guaranteed copy elision" }
可能的输出
constructed at 0x7fffd635fd4e &v = 0x7fffd635fd4e constructed at 0x7fffd635fd4f &arg = 0x7fffd635fd4f destructed at 0x7fffd635fd4f destructed at 0x7fffd635fd4e
[编辑] 缺陷报告
以下行为更改缺陷报告被追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 1967 | C++11 | 当使用移动构造函数完成复制省略时, 移动源对象的生命周期仍然被考虑 |
不被考虑 |
CWG 2426 | C++17 | 返回纯右值时不需要析构函数 | 析构函数可能被调用 |
CWG 2930 | C++98 | 只能省略复制(/移动)操作,但是 非复制(/移动)构造函数可以通过复制初始化来选择 |
省略任何对象构造 相关的复制初始化 |