保证副本省略

Version >= C++ 17

通常,elision 是一种优化。虽然在最简单的情况下几乎每个编译器都支持复制省略,但是 elision 仍然给用户带来了特别的负担。也就是说,被删除的复制/移动类型必须仍然具有被删除的复制/移动操作。

例如:

std::mutex a_mutex;
std::lock_guard<std::mutex> get_lock()
{
  return std::lock_guard<std::mutex>(a_mutex);
}

如果 a_mutex 是某个系统私有的互斥锁,但外部用户可能希望对其进行范围锁定,则这可能很有用。

这也不合法,因为 std::lock_guard 无法复制或移动。尽管几乎每个 C++编译器都会忽略复制/移动,但标准仍然需要该类型才能使该操作可用。

直到 C++ 17。

C++ 17 通过有效地重新定义某些表达式的含义来强制执行,从而不会发生复制/移动。考虑上面的代码。

在 pre-C++ 17 的措辞下,该代码表示​​创建一个临时的然后使用临时复制/移动到返回值,但临时副本可以省略。根据 C++ 17 的措辞,这根本不会产生临时性。

在 C++ 17 中,任何 prvalue 表达式在用于初始化与表达式相同类型的对象时都不会生成临时表达式。表达式直接初始化该对象。如果返回与返回值相同类型的 prvalue,则该类型不需要具有复制/移动构造函数。因此,根据 C++ 17 规则,上述代码可以工作。

在 prvalue 的类型与正在初始化的类型匹配的情况下,C++ 17 的措辞有效。所以给定 get_lock,这也不需要复制/移动:

std::lock_guard the_lock = get_lock();

由于 get_lock 的结果是用于初始化相同类型的对象的 prvalue 表达式,因此不会发生复制或移动。那种表达永远不会造成暂时的; 它用于直接初始化 the_lock。没有任何缺点,因为没有副本/移动被省略。

因此,保证副本省略这个术语有点用词不当,但这就是为 C++ 17 标准化提出的功能名称 。它根本不保证省略; 它完全消除了复制/移动,重新定义了 C++,因此从未有过复制/移动。

此功能仅适用于涉及 prvalue 表达式的情况。因此,这使用通常的省略规则:

std::mutex a_mutex;
std::lock_guard<std::mutex> get_lock()
{
  std::lock_guard<std::mutex> my_lock(a_mutex);
  //Do stuff
  return my_lock;
}

虽然这是复制省略的有效案例,但在这种情况下,C++ 17 规则并不能消除复制/移动。因此,类型必须仍具有用于初始化返回值的复制/移动构造函数。由于 lock_guard 没有,这仍然是编译错误。允许实现在传递或返回普通可复制类型的对象时拒绝删除副本。这是为了允许在寄存器中移动这些对象,一些 ABI 可能在其调用约定中强制要求。

struct trivially_copyable {
    int a;  
};

void foo (trivially_copyable a) {}

foo(trivially_copyable{}); //copy elision not mandated