复制省略的目的

标准中有一些位置可以复制或移动对象以初始化对象。复制省略(有时称为返回值优化)是一种优化,在某些特定情况下,即使标准表明它必须发生,也允许编译器避免复制或移动。

考虑以下功能:

std::string get_string()
{
  return std::string("I am a string.");
}

根据标准的严格措辞,此函数将初始化临时 std::string,然后将其复制/移动到返回值对象中,然后销毁临时值。标准非常明确,这就是代码的解释方式。

复制省略是允许 C++编译器忽略临时及其后续复制/销毁的创建的规则。也就是说,编译器可以为临时表达初始化表达式,并直接从中初始化函数的返回值。这显然可以节省性能。

但是,它对用户有两个可见的影响:

  1. 该类型必须具有已被调用的复制/移动构造函数。即使编译器省略了复制/移动,该类型仍必须能够被复制/移动。

  2. 在可能发生 elision 的情况下,不保证复制/移动构造函数的副作用。考虑以下:

Version >= C++ 11

struct my_type
{
  my_type() = default;
  my_type(const my_type &) {std::cout <<"Copying\n";}
  my_type(my_type &&) {std::cout <<"Moving\n";}
};

my_type func()
{
  return my_type();
}

什么叫 func 呢?好吧,它永远不会打印复制,因为临时是一个右值,而 my_type 是一个可移动的类型。它会打印移动吗?

如果没有复制省略规则,则需要始终打印移动。但由于存在复制省略规则,移动构造函数可能会被调用,也可能不会被调用; 它依赖于实现。

因此,你不能依赖于在可以省略的情况下调用复制/移动构造函数。

因为 elision 是一种优化,所以在所有情况下,编译器可能都不支持 elision。并且无论编译器是否省略特定情况,该类型仍必须支持被省略的操作。因此,如果省略了复制构造,则类型必须仍具有复制构造函数,即使它不会被调用。