在过去的一天左右,我一直在学习移动构造函数,试图坚持按照大多数人似乎建议的价值返回的一般规则,并且遇到了一个有趣的(对我来说)困境。
假设我有一个昂贵的构造/复制类'C',它具有正确定义的复制构造函数,赋值运算符,移动构造函数和移动赋值运算符。
首先,这段代码省略了我预期的复制构造函数:
C make_c1() {
return C();
}
就像这样:
C make_c2() {
C tmp;
return tmp;
}
这样做(无论我传入1还是2):
C make_c3(int a) {
return a == 1 ? make_c1() : make_c2();
}
当我遇到这个问题时,我遇到了一个问题:
C make_c4(int a) {
C tmp;
return a == 1 ? make_c1() : tmp;
}
传入1会触发RVO以获取make_c1的结果,但传入2会触发tmp上的复制构造函数。
将函数修改为以下内容会导致为tmp触发移动构造函数:
C make_c5(int a) {
C tmp;
return a == 1 ? make_c1() : std::move(tmp);
}
除了......之外,一切都很美好。
在这些简单的例子中,RVO已被触发,就像我希望的那样。
但是,如果我的代码稍微复杂一些,并且某些编译器在最后一个函数中没有唤起RVO怎么办?在这种情况下,我需要在std :: move中包含对make_c1的调用,这将使那些引起RVO的编译器的代码效率降低。
所以我的问题是:
我一直在玩的编译器是关于Cygwin的GCC 4.5.3。
答案 0 :(得分:7)
隐含的收益回报仅在RVO合法的相同背景下合法。当表达式是一个非易失性自动对象(函数或catch子句参数除外)的名称时,RVO是合法的,它具有与函数返回类型相同的cv-非限定类型([class.copy] / p31 / b1 )。
如果您将make_c4
转换为:
C make_c4(int a) {
C tmp;
if (a == 1)
return make_c1();
return tmp;
}
然后,您获得了make_c4(2)
号召唤的预期移动结构。完全由您说明的原因,您的make_c5
重写是不可取的。
<强>更新强>
我还应该包含对[expr.cond] / p6 / b1的引用,它解释了当第二个表达式是prvalue而第三个是lvalue时条件表达式的语义,但它们都具有相同的类型: / p>
第二个和第三个操作数具有相同的类型;结果是 那种类型。如果操作数具有类类型,则结果为prvalue 结果类型的临时,从任一个复制初始化 第二个操作数或第三个操作数取决于的值 第一个操作数。
即。此段落指定条件的结果prvalue是 copy-initialized ,来自示例中的第3个参数。 复制初始化在[dcl.init] / p14中定义。当复制初始化的源是类型左值时,这将调用类型的复制构造函数。如果源是rvalue,它将调用移动构造函数(如果存在),否则它将调用复制构造函数。
条件表达式的规范不允许从lvalue参数隐式移动,即使条件表达式是返回表达式的一部分。这种语言有可能被制作成允许这种隐含的举动,但据我所知,它从未提出过。此外,条件表达式的现有规范已经非常复杂,使得对语言的这种改变变得更加困难。