命名空间
变体
操作

复制省略

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
范围 for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (在 C++17 中弃用*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储期说明符
初始化
 
 

当满足特定条件时,可以省略从相同类型的源对象(忽略 cv 限定)创建类对象的操作,即使所选的构造函数和/或对象的析构函数具有副作用。这种省略对象创建的操作称为复制省略

目录

[编辑] 解释

在以下情况下允许复制省略(可以组合使用以消除多个副本)

  • 在具有类返回类型的函数中的 return 语句 中,当操作数是具有自动存储期(函数形参或 处理程序 形参除外)的非易失性对象 obj 的名称时,可以通过将 obj 直接构造到函数调用的结果对象中来省略结果对象的复制初始化。这种复制省略的变体称为具名返回值优化 (NRVO)。
  • 当使用未绑定到引用的临时类对象 obj 复制初始化类对象 target 时,可以通过将 obj 直接构造到 target 中来省略复制初始化。这种复制省略的变体称为未命名返回值优化 (URVO)。自 C++17 起,URVO 是强制性的,不再被视为复制省略的一种形式;见下文。
(C++17 前)
  • throw 表达式 中,当操作数是属于不包含最内层封闭 try(如果存在)的作用域的具有自动存储期(函数形参或处理程序形参除外)的非易失性对象 obj 的名称时,可以通过将 obj 直接构造到异常对象中来省略异常对象的复制初始化。
  • 处理程序 中,如果程序的含义不会改变,除非执行处理程序实参的构造函数和析构函数,否则可以通过将处理程序形参视为异常对象的别名来省略处理程序实参的复制初始化。
(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
}
返回类型的析构函数必须在 return 语句的点上可访问且非已删除,即使没有 T 对象被销毁。
  • 在对象的初始化中,当初始化器表达式是与变量类型相同类类型(忽略 cv 限定)的 纯右值
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 只能省略复制(/移动)操作,但是
非复制(/移动)构造函数可以通过复制初始化来选择
省略任何对象构造
相关的复制初始化

[编辑] 参见