RVO,移动操作和进退两难

Ian*_*ix1 8 c++ c++11

在过去的一天左右,我一直在学习移动构造函数,试图坚持按照大多数人似乎建议的价值返回的一般规则,并且遇到了一个有趣的(对我而言)困境.

假设我有一个昂贵的构造/复制类'C',它具有正确定义的复制构造函数,赋值运算符,移动构造函数和移动赋值运算符.

首先,这段代码省略了我预期的复制构造函数:

C make_c1() {
    return C();
}
Run Code Online (Sandbox Code Playgroud)

就像这样:

C make_c2() {
    C tmp;
    return tmp;
}
Run Code Online (Sandbox Code Playgroud)

这样做(无论我传入1还是2):

C make_c3(int a) {
    return a == 1 ? make_c1() : make_c2();
}
Run Code Online (Sandbox Code Playgroud)

我遇到这个问题时,我遇到了一个问题:

C make_c4(int a) {
    C tmp;
    return a == 1 ? make_c1() : tmp;
}
Run Code Online (Sandbox Code Playgroud)

传入1会触发RVO以获取make_c1的结果,但传入2将触发tmp上的复制构造函数.

将函数修改为以下内容会导致为tmp触发移动构造函数:

C make_c5(int a) {
    C tmp;
    return a == 1 ? make_c1() : std::move(tmp);
}
Run Code Online (Sandbox Code Playgroud)

一切都非常精彩,除了......

在这些简单的例子中,RVO已被触发,就像我希望的那样.

但是,如果我的代码稍微复杂一些,并且某些编译器在最后一个函数中没有引起RVO呢?在这种情况下,我需要在std :: move中包含对make_c1的调用,这会使代码在那些唤起RVO的编译器上效率降低.

所以我的问题是:

  1. 当我返回本地对象时,为什么在make_c4中没有调用move构造函数?(毕竟它即将被摧毁).
  2. 在函数make_c5中,我应该按值返回make_c1的结果还是移动它?(为了避免不同编译器/平台的不同版本的代码).
  3. 是否有更好的方法来编写最终函数,以便它为合理的编译器实现做正确的事情?

我一直在玩的编译器是Cygwin上的GCC 4.5.3.

How*_*ant 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;
}
Run Code Online (Sandbox Code Playgroud)

然后你得到了呼叫的预期移动结构make_c4(2).make_c5由于您说明的原因,您的重写是不可取的.

更新:

我还应该包含对[expr.cond]/p6/b1的引用,它解释了当第二个表达式是prvalue而第三个是左值时条件表达式的语义,但它们都具有相同的类型:

第二和第三个操作数具有相同的类型; 结果是那种类型.如果操作数具有类类型,则结果是结果类型的prvalue临时值,它根据第一个操作数的值从第二个操作数或第三个操作数进行复制初始化.

即本段规定条件的结果prvalue是从示例中的第3个参数复制初始化的. 复制初始化在[dcl.init]/p14中定义.当复制初始化的源是类型左值时,这将调用类型的复制构造函数.如果源是rvalue,它将调用移动构造函数(如果存在),否则它将调用复制构造函数.

即使条件表达式是返回表达式的一部分,条件表达式的规范也不允许从lvalue参数隐式移动.这种语言有可能被制作成允许这种隐含的举动,但据我所知,它从未提出过.此外,条件表达式的现有规范已经非常复杂,使得对语言的这种改变变得更加困难.