移动语义与(N)RVO一起的有效使用 [英] Efficient use of move semantics together with (N)RVO

查看:128
本文介绍了移动语义与(N)RVO一起的有效使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我想实现一个函数,该函数应该处理一个对象并返回一个新的(可能已更改的)对象。我想在C + 11尽可能高效。环境如下:

Let's say I want to implement a function that is supposed to process an object and return a new (possibly changed) object. I would like to do this as efficient as possible in C+11. The environment is as follows:

class Object {
    /* Implementation of Object */
    Object & makeChanges();
};

我想到的替代方法是:

// First alternative:
Object process1(Object arg) { return arg.makeChanges(); }
// Second alternative:
Object process2(Object const & arg) { return Object(arg).makeChanges(); }
Object process2(Object && arg) { return std::move(arg.makeChanges()); }
// Third alternative:
Object process3(Object const & arg) { 
    Object retObj = arg; retObj.makeChanges(); return retObj; 
}
Object process3(Object && arg) { std::move(return arg.makeChanges()); }

注意:我想使用 因为它会做一些其他工作,我想尽可能多的代码重用。

Note: I would like to use a wrapping function like process() because it will do some other work and I would like to have as much code reuse as possible.

更新:

我使用给定签名的 makeChanges(),因为我处理的对象提供了签名类型。我想他们使用它为方法链。我还修正了上面提到的两个语法错误。感谢您指出那些。我还添加了第三个选项,我会安排以下问题。

I used the makeChanges() with the given signature because the objects I am dealing with provides methods with that type of signature. I guess they used that for method chaining. I also fixed the two syntax errors mentioned. Thanks for pointing those out. I also added a third alternative and I will repose the question below.

尝试这些与clang [ Object obj2 = process(obj); ]会导致以下结果:

Trying these out with clang [i.e. Object obj2 = process(obj);] results in the following:

构造函数一个用于传递参数,一个用于返回。可以改为 return std :: move(..),并对复制构造函数和对move构造函数的一次调用。我理解RVO不能摆脱这些调用,因为我们处理的函数参数。

First option makes two calls to the copy constructor; one for passing the argument and one for returning. One could instead say return std::move(..) and have one call to the copy constructor and one call to the move constructor. I understand that RVO can not get rid of one of these calls because we are dealing with the function parameter.

在第二个选项,我们仍然有两个调用的副本构造函数。这里我们做一个显式调用,一个返回时。我期待RVO踢,摆脱后者,因为我们返回的对象是一个不同的对象比参数。但是,它没有发生。

In the second option, we still have two calls to the copy constructor. Here we make one explicit call and one is made while returning. I was expecting for RVO to kick in and get rid of the latter since the object we are returning is a different object than the argument. However, it did not happen.

在第三个选项中,我们只有一个对复制构造函数的调用,这是显式的。 (N)RVO消除了我们要返回的复制构造函数调用。

In the third option we have only one call to the copy constructor and that is the explicit one. (N)RVO eliminates the copy constructor call we would do for returning.

我的问题如下:


  1. (回答)为什么RVO在最后一个选项而不是第二个选项?

  2. 有更好的方法吗?

  3. 如果我们传入一个临时的,第二和第三个选项将返回时调用移动构造函数。是否可以消除使用(N)RVO?

谢谢!

推荐答案

我喜欢测量,所以我设置对象

I like to measure, so I set up this Object:

#include <iostream>

struct Object
{
    Object() {}
    Object(const Object&) {std::cout << "Object(const Object&)\n";}
    Object(Object&&) {std::cout << "Object(Object&&)\n";}

    Object& makeChanges() {return *this;}
};

我认为一些解决方案可能给出xvalues和prvalues的不同答案(两者都是右值) 。所以我决定测试它们(除了左值):

And I theorized that some solutions may give different answers for xvalues and prvalues (both of which are rvalues). And so I decided to test both of them (in addition to lvalues):

Object source() {return Object();}

int main()
{
    std::cout << "process lvalue:\n\n";
    Object x;
    Object t = process(x);
    std::cout << "\nprocess xvalue:\n\n";
    Object u = process(std::move(x));
    std::cout << "\nprocess prvalue:\n\n";
    Object v = process(source());
}

现在这是一个简单的事情,尝试所有的可能性,其他人,我自己投掷一个:

Now it is a simple matter of trying all of your possibilities, those contributed by others, and I threw one in myself:

#if PROCESS == 1

Object
process(Object arg)
{
    return arg.makeChanges();
}

#elif PROCESS == 2

Object
process(const Object& arg)
{
    return Object(arg).makeChanges();
}

Object
process(Object&& arg)
{
    return std::move(arg.makeChanges());
}

#elif PROCESS == 3

Object
process(const Object& arg)
{
    Object retObj = arg;
    retObj.makeChanges();
    return retObj; 
}

Object
process(Object&& arg)
{
    return std::move(arg.makeChanges());
}

#elif PROCESS == 4

Object
process(Object arg)
{
    return std::move(arg.makeChanges());
}

#elif PROCESS == 5

Object
process(Object arg)
{
    arg.makeChanges();
    return arg;
}

#endif

(使用clang -std = c ++ 11)。第一个数字是复制结构的数量,第二个数字是移动结构的数量:

The table below summarizes my results (using clang -std=c++11). The first number is the number of copy constructions and the second number is the number of move constructions:

+----+--------+--------+---------+
|    | lvalue | xvalue | prvalue |    legend: copies/moves
+----+--------+--------+---------+
| p1 |  2/0   |  1/1   |   1/0   |
+----+--------+--------+---------+
| p2 |  2/0   |  0/1   |   0/1   |
+----+--------+--------+---------+
| p3 |  1/0   |  0/1   |   0/1   |
+----+--------+--------+---------+
| p4 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+
| p5 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+

process3 看起来像是我最好的解决方案。然而,它需要两个重载。一个用于处理左值,一个用于处理右值。如果由于某种原因这是有问题的,解决方案4和5做的工作只有一个超载,代价是对glvalues(lvalue和xvalue)执行1个额外的移动构造。这是一个判断呼叫,是否想支付额外的移动建设以节省超载(并没有一个正确的答案)。

process3 looks like the best solution to me. However it does require two overloads. One to process lvalues and one to process rvalues. If for some reason this is problematic, solutions 4 and 5 do the job with only one overload at the cost of 1 extra move construction for glvalues (lvalues and xvalues). It is a judgement call as to whether one wants to pay an extra move construction to save overloading (and there is no one right answer).


(回答)为什么RVO在最后一个选项而不是第二个选项?

(answered) Why does RVO kick in the last option and not the second?

对于RVO,看起来像:

For RVO to kick in, the return statement needs to look like:

return arg;

如果您使用以下方法复杂化:

If you complicate that with:

return std::move(arg);

或:

return arg.makeChanges();

然后RVO被禁止。


有更好的方法吗?

Is there a better way to do this?

我最喜欢的是p3和p5。我喜欢p5比p4只是文体。当我知道将会自动应用 move return 语句时,恐怕会意外地禁止RVO 。然而在p5 RVO不是一个选项反正,即使return语句确实得到一个隐式移动。所以p5和p4真的是等价的。选择你的风格。

My favorites are p3 and p5. My preference of p5 over p4 is merely stylistic. I shy away from putting move on the return statement when I know it will be applied automatically for fear of accidentally inhibiting RVO. However in p5 RVO is not an option anyway, even though the return statement does get an implicit move. So p5 and p4 really are equivalent. Pick your style.


如果我们通过一个临时的,第二和第三选项,在返回时会调用
构造函数。是否可以消除使用
(N)RVO?

Had we passed in a temporary, 2nd and 3rd options would call a move constructor while returning. Is is possible to eliminate that using (N)RVO?

prvalue列与xvalue列地址这个问题。一些解决方案为xvalues添加了一个额外的移动结构,有些解决方案没有。

The "prvalue" column vs "xvalue" column addresses this question. Some solutions add an extra move construction for xvalues and some don't.

这篇关于移动语义与(N)RVO一起的有效使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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