RVO,移动操作和困境 [英] RVO, move operations and a dilemma

查看:112
本文介绍了RVO,移动操作和困境的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have been learning about move constructors over the last day or so, trying to stick to a general rule of returning by value as most people seem to suggest, and have come across an interesting (to me) dilemma.

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

Assume that I have an expensive to construct/copy class 'C' that has correctly defined copy constructor, assignment operator, move constructor and move assignment operator.

首先,这段代码像我预期的那样消除了复制构造函数:

First, this piece of code elides the copy constructor as I expected:

C make_c1() {
    return C();
}

C make_c2() {
    C tmp;
    return tmp;
}

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

and so does this (whether I pass in a 1 or 2):

C make_c3(int a) {
    return a == 1 ? make_c1() : make_c2();
}

这是当我遇到一个问题:

It's when I get to this that I have an issue:

C make_c4(int a) {
    C tmp;
    return a == 1 ? make_c1() : tmp;
}

传入1触发RVO获取make_c1的结果, 2触发tmp上的复制构造函数。

Passing in a 1 triggers RVO for the result of make_c1, but passing in a 2 triggers the copy constructor on tmp.

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

Amending the function to the following causes the move constructor to be triggered for tmp instead:

C make_c5(int a) {
    C tmp;
    return a == 1 ? make_c1() : std::move(tmp);
}

除了...之外,所有伟大和美好的

All great and wonderful except...

在这些简单的例子中,RVO被触发的程度与我所希望的一样。

In these simple examples, RVO has been triggered pretty much as I'd hoped.

但是如果我的代码稍微复杂一些一些编译器不会在最后一个函数中唤起RVO?在这种情况下,我需要在std :: move中调用make_c1,这将使代码在那些能唤起RVO的编译器上效率较低。

However what if my code is slightly more complex and on some compilers doesn't evoke RVO in that last function? In that case, I'd need to wrap my call to make_c1 in std::move, which will make the code less efficient on those compilers that do evoke RVO.

所以我的问题是:


  1. 为什么当我返回我的本地对象时,在make_c4中不调用move构造函数?

  2. 在函数make_c5中,应该通过值还是通过移动它来返回make_c1的结果? (为了避免不同编译器/平台的不同版本的代码)。

  3. 是否有更好的方法来编写最终的函数,以便为合理的编译器实现正确的方法? / li>
  1. Why was the move constructor not invoked in make_c4 when I returned my local object? (It's about to be destroyed after all).
  2. In the function make_c5, should I return the results of make_c1 by value or by moving it? (To avoid different versions of the code for differing compilers/platforms).
  3. Is there a better way to code the final function so that it does the right thing for a reasonable compiler implementation?

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

The compiler I have been playing with is GCC 4.5.3 on Cygwin.

推荐答案

隐含的回归移动在RVO合法的相同上下文中是合法的。当表达式是具有与函数返回类型([class.copy] / p31 / b1)相同的cv-unalalified类型的非易失性自动对象(除了函数或catch子句参数之外)的名称时,RVO是合法的)。

The implicit move-on-return is only legal in the same contexts in which RVO is legal. And RVO is legal when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv- unqualified type as the function return type ([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时的条件表达式的语义,第三个是一个左值,但两者都具有相同的类型:

I should have also included a reference to [expr.cond]/p6/b1 which explains the semantics of the conditional expression when the second expression is a prvalue and the third is an lvalue, but both have the same type:


第二个和第三个操作数具有相同的类型;结果是
的类型。如果操作数具有类类型,结果是结果类型的prvalue
临时,它是从
第二个操作数或第三个操作数复制初始化的,取决于
的值第一个操作数。

The second and third operands have the same type; the result is of that type. If the operands have class type, the result is a prvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand.

这一段指定了条件的结果prvalue是从你的示例中的第三个参数的复制初始化。在[dcl.init] / p14中定义了复制初始化。当副本初始化的源是类类型的左值时,这将调用类型的副本构造函数。如果源是一个右值,它将调用move构造函数,如果存在,否则它将调用复制构造函数。

I.e. this paragraph specifies that the resultant prvalue of the conditional is copy-initialized, from the 3rd argument in your example. Copy-initialization is defined in [dcl.init]/p14. When the source of a copy-initialization is a class-type lvalue, this will invoke the type's copy constructor. If the source is an rvalue, it will invoke the move constructor if one exists, else it will invoke the copy constructor.

条件表达式的规定不允许即从左值参数的隐式移动,即使条件表达式是返回表达式的一部分。这种语言可能是为了允许这样一个隐含的移动,但据我所知,它从来没有被提出过。此外,条件表达式的现有规范已经非常复杂,使得对语言的这种改变变得更加困难。

The specification of the conditional expression has no allowance for an implicit move from an lvalue argument, even if the conditional expression is part of a return expression. It is possible that the language could have been crafted to allow such an implicit move, but as far as I know, it was never proposed. Furthermore the existing specification of the conditional expression is already extremely complicated, making such change to the language all the more difficult.

这篇关于RVO,移动操作和困境的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆