复制Elision和右值参考

时间:2011-04-15 09:44:40

标签: c++ c++11

在下面的代码中,它用于避免复制,省略或右值引用和移动构造函数?

std::string get(){return "...";}

void foo(std::string var){}

foo( get() ); //<--- here

3 个答案:

答案 0 :(得分:9)

std::string get(){
    // this is similar to return std::string("..."), which is
    // copied/moved into the return value object.
    return "...";
}

RVO允许它将临时字符串对象直接构造到get()的返回值对象中。

foo( get() );

RVO允许它直接将临时字符串对象(返回值对象)直接构造到foo的参数对象中。

这些是允许的RVO方案。如果编译器无法应用它们,则 使用移动构造函数(如果可用)将返回值分别移动到返回值对象和参数对象中。在这种情况下,这并不奇怪,因为无论如何临时对象都被视为rvalues。 (对于第一种情况,没有表达式对应于创建的临时表,因此处理仅用于选择用于复制/移动临时值到返回值对象的构造函数)。

对于其他情况,编译器必须将事物视为rvalues,即使它们是lvalues

std::string get(){
    std::string s = "...";
    // this is similar to return move(s)
    return s;
}

规范说当它可能通过它提出的规则将RVO(或NRVO)应用于左值时,需要实现将表达式视为rvalues并使用移动构造函数(如果可用),并且仅当它不能找到合适的构造函数,它应该使用表达式作为左值。对于程序员来说,在这些情况下编写显式移动是很可惜的,因为很明显程序员总是想要移动而不是复制。

示例:

struct A { A(); A(A&); };
struct B { B(); B(B&&); };

A f() { A a; return a; }
B f() { B b; return b; }

对于第一个,它将a作为右值,但找不到接受此右值的构造函数(A&无法绑定到rvalues)。因此,它再次将a视为它(左值)。对于第二个,它需要b作为右值,并且B(B&&)接受该右值并移动它。如果将b作为左值(它是什么),则复制初始化将失败,因为B没有隐式声明的复制构造函数。


请注意,return和paramter传递使用复制初始化的规则,这意味着

u -> T (where u's type is different from T) =>
  T rvalue_tmp = u;
  T target(rvalue_tmp);

t -> T (where t's type is T) =>
  T target = t;

因此,在我们返回"..."的示例中,我们首先创建一个rvalue临时值,然后将其移动到目标中。对于返回返回值/参数类型的表达式的情况,我们将直接将表达式移动/复制到目标中。

答案 1 :(得分:2)

最有可能复制椭圆,但是如果你的编译器不适用于这种情况,如果函数更复杂就会发生这种情况,那么你就是在考虑移动。移动非常有效,所以如果不执行椭圆,我不会在这里感到恐慌。

答案 2 :(得分:1)

已定义实施,但最有可能复制省略。

类似地,当从函数返回对象值时,RVO / NRVO很可能在移动语义之前启动。