复制省略

来自cppreference.com
< cpp‎ | language
 
 
C++ 语言
表达式
替代表示
字面量
布尔 - 整数 - 浮点
字符 - 字符串 - nullptr (C++11)
用户定义 (C++11)
工具设施
特性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
强制转换
隐式类型转换 - 显式类型转换
static_cast - dynamic_cast
const_cast - reinterpret_cast
内存分配
类特有的函数属性
特殊成员函数
模板
杂项
 

将复制和移动 (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++11 起)构造函数,即使复制/移动 (C++11 起)构造函数和析构函数拥有可见的副效应:

  • 若函数以值返回一个类型,且return语句的表达式是拥有自动存储期的非volatile对象之名,且该对象不是函数参数或catch子句的参数,且拥有与函数返回类型相同的类型(忽略顶层cv资格),则复制/移动 (C++11 起)被省略。当创建局部对象时,它直接被构造于函数返回值本来要移动或 (C++11 起)复制到的存储位置。此复制省略的变体称为NRVO,“具名返回值优化(named return value optimization)”。
  • 当未被绑定到任何引用的无名临时量,会被移动或复制到同一类型的对象时(忽略顶层cv资格),则复制/移动被忽略。在构造临量时,它直接被构造到本来要移动或复制到的存储位置。当该无名临时量是返回语句的参数是,此复制省略的变体称为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 起)构造函数及析构函数副作用的程序是不可移植的。

在复制省略发生 (C++17 前)在不保证复制省略的场合,若它发生 (C++17 起)且不调用复制/移动构造函数时,它必须存在且可访问(宛如完全没有发生优化),否则程序是病态的。

在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); // 从临时量初始化v时复制省略
                                                  // (C++17中保证)
    return v; // NRVO从v到返回的无名临时量(C++17中不保证)
}             // 或在优化被禁用时调用移动构造函数
 
void g(std::vector<Noisy> arg)
{
    std::cout << "arg.size() = " << arg.size() << '\n';
}
 
int main()
{
    std::vector<Noisy> v = f(); // 从f()的结果初始化v中的复制省略(C++17中保证)
    g(f());                     // 从f()的结果初始化g()参数的中的复制省略(C++17中保证)
}

可能的输出:

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


[编辑] 缺陷报告

以下行为变化的缺陷报告可追溯至以前发布的 C++ 标准。

报告 适用于 已发布的行为 正确行为
CWG 2022 C++14 复制省略在常量表达式中可选 强制复制省略

[编辑] 参阅