重载算法中的移动语义和右值引用 [英] Move Semantics and Pass-by-Rvalue-Reference in Overloaded Arithmetic

查看:95
本文介绍了重载算法中的移动语义和右值引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在用C ++编写一个小的数值分析库.我一直在尝试使用最新的C ++ 11功能(包括移动语义)来实现.我了解以下帖子中的讨论和最佳答案: C ++ 11右值并移动语义混乱(返回语句),但是在一种情况下,我仍在努力寻找解决方法.

I am coding a small numeric analysis library in C++. I have been trying to implement using the latest C++11 features including move semantics. I understand the discussion and top answer at the following post: C++11 rvalues and move semantics confusion (return statement) , but there is one scenario that I still am trying to wrap my head around.

我有一个课,叫它T,它完全装有重载运算符.我也有复制和移动构造函数.

I have a class, call it T, which is fully equipped with overloaded operators. I also have both copy and move constructors.

T (const T &) { /*initialization via copy*/; }
T (T &&) { /*initialization via move*/; }

我的客户代码大量使用运算符,因此我试图确保复杂的算术表达式从移动语义中获得最大的收益.请考虑以下内容:

My client code heavily uses operators, so I am trying to ensure that complex arithmetic expressions get maximum benefit from move semantics. Consider the following:

T a, b, c, d, e;
T f = a + b * c - d / e;

没有移动语义,我的操作员每次都使用copy构造函数创建一个新的局部变量,因此总共有4个副本.我希望通过移动语义可以将其减少到2个副本以及一些移动.在带括号的版本中:

Without move semantics, my operators are making a new local variable using the copy constructor each time, so there are a total of 4 copies. I was hoping that with move semantics I could reduce this to 2 copies plus some moves. In the parenthesized version:

T f = a + (b * c) - (d / e);

(b * c)(d / e)的每一个都必须以通常的方式创建一个临时副本,但是如果我可以利用这些临时之一来仅移动就可以累积剩余的结果,那就太好了.

each of (b * c) and (d / e) must create the temporary in the usual way with a copy, but then it would be great if I could leverage one of those temporaries to accumulate the remaining results with only moves.

使用g ++编译器,我已经能够做到这一点,但是我怀疑我的技术可能并不安全,我想完全理解为什么.

Using g++ compiler, I have been able to do this, but I suspect my technique may not be safe and I want to fully understand why.

这是加法运算符的示例实现:

Here is an example implementation for the addition operator:

T operator+ (T const& x) const
{
    T result(*this);
    // logic to perform addition here using result as the target
    return std::move(result);
}
T operator+ (T&& x) const
{
    // logic to perform addition here using x as the target
    return std::move(x);
}

如果没有对std::move的调用,则只会调用每个运算符的const &版本.但是,当如上使用std::move时,将使用每个运算符的&&版本执行后续算术(在最里面的表达式之后).

Without the calls to std::move, then only the const & version of each operator is ever invoked. But when using std::move as above, subsequent arithmetic (after the innermost expressions) are performed using the && version of each operator.

我知道RVO可以被抑制,但是在计算上非常昂贵的现实问题中,增益似乎略大于RVO的不足.也就是说,当包含std::move时,我确实获得了非常小的加速,超过了数百万次的计算.尽管老实说,没有它就足够快了.我真的只是想完全理解这里的语义.

I know that RVO can be inhibited, but on very computationally-expensive, real-world problems it seems that the gain slightly outweighs the lack of RVO. That is, over millions of computations I do get a very tiny speedup when I include std::move. Though in all honesty it is fast enough without. I really just want to fully comprehend the semantics here.

有没有一种C ++专家愿意花时间以一种简单的方式来说明我在这里是否以及为什么使用std :: move是一件坏事?预先非常感谢.

Is there a kind C++ Guru who is willing to take the time to explain, in a simple way, whether and why my use of std::move is a bad thing here? Many thanks in advance.

推荐答案

您应该更喜欢将运算符重载为自由函数,以获取完整的类型对称性(可以在左侧和右侧应用相同的转换).这使问题变得更加显而易见.将您的操作员恢复为您提供的免费功能:

You should prefer overloading the operators as free functions to obtain full type symmetry (same conversions can be applied on the left and right hand side). That makes it a bit more obvious what you are missing from the question. Restating your operator as free functions you are offering:

T operator+( T const &, T const & );
T operator+( T const &, T&& );

但是您没有提供一个可以解决左侧问题的版本:

But you are failing to provide a version that handles the left hand side being a temporary:

T operator+( T&&, T const& );

为避免两个参数均为右值时代码中的歧义,您需要提供另一个重载:

And to avoid ambiguities in the code when both arguments are rvalues you need to provide yet another overload:

T operator+( T&&, T&& );

通常的建议是将+=作为实现修改当前对象的成员方法,然后将operator+作为转发器编写,从而修改接口中的适当对象.

The common advice would be to implement += as a member method that modifies the current object, and then write operator+ as a forwarder that modifies the appropriate object in the interface.

我还没有真正考虑过很多,但是可能会使用T(没有r/lvalue引用),但是我担心它不会减少为制作operator+而需要提供的重载次数.在任何情况下都是有效的.

I have not really thought this much, but there might be an alternative using T (no r/lvalue reference), but I fear that it will not reduce the number of overloads you need to provide to make operator+ efficient in all circumstances.

这篇关于重载算法中的移动语义和右值引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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