复制省略的目的
标准中有一些位置可以复制或移动对象以初始化对象。复制省略(有时称为返回值优化)是一种优化,在某些特定情况下,即使标准表明它必须发生,也允许编译器避免复制或移动。
考虑以下功能:
std::string get_string()
{
return std::string("I am a string.");
}
根据标准的严格措辞,此函数将初始化临时 std::string
,然后将其复制/移动到返回值对象中,然后销毁临时值。标准非常明确,这就是代码的解释方式。
复制省略是允许 C++编译器忽略临时及其后续复制/销毁的创建的规则。也就是说,编译器可以为临时表达初始化表达式,并直接从中初始化函数的返回值。这显然可以节省性能。
但是,它对用户有两个可见的影响:
-
该类型必须具有已被调用的复制/移动构造函数。即使编译器省略了复制/移动,该类型仍必须能够被复制/移动。
-
在可能发生 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。并且无论编译器是否省略特定情况,该类型仍必须支持被省略的操作。因此,如果省略了复制构造,则类型必须仍具有复制构造函数,即使它不会被调用。