【问题标题】:Symmetric operator+ in terms of operator+= in modern C++?就现代 C++ 中的 operator+= 而言,对称 operator+?
【发布时间】:2019-01-18 03:02:14
【问题描述】:

我正在阅读这篇关于在 Boost.Operator https://www.boost.org/doc/libs/1_69_0/libs/utility/operators.htm#symmetry 中实现对称运算符的说明,我怀疑它已经过时了。

讨论的重点是什么是一般实现operator+ 的最佳方式,如果一致的operator+= 可用。那里的结论是它是(曾经),

T operator+( const T& lhs, const T& rhs ){
   T nrv( lhs ); nrv += rhs; return nrv;
}

因为当时一些编译器支持 NRVO,而不是 RVO。

现在, NRVO 是强制性的, 正在执行各种优化,这仍然是这种情况吗?

例如,在某些情况下现在可能有意义的其他版本是:

    T operator+(T lhs, const T& rhs ){
       T ret(std::move(lhs)); ret += rhs; return ret;
    }

    T operator+(T lhs, const T& rhs ){
      lhs += rhs; return lhs;
    }

给定一个具有构造函数、移动构造函数和合理operator+= 的类。例如:

#include<array>
#include<algorithm>

using element = double; // here double, but can be more complicated
using array = std::array<double, 9>; // here array, but can be more complicated

array& operator+=(array& a, array const& b){
    std::transform(begin(a), end(a), begin(b), begin(a), [](auto&& x, auto&& y){return x + y;});
    return a;
}
array& operator+=(array&& a, array const& b){
    std::transform(begin(a), end(a), begin(b), begin(a), [](auto&& x, auto&& y){return x + std::move(y);});
    return a;
}

实现对称operator+ 的最佳方法是什么?这是一组可能的代码

/*1*/ array sum(array const& a, array const& b){array tmp(a); tmp+=b; return tmp;} // need operator+= and copy-constructor
/*2*/ array sum(array const& a, array const& b){return array(a)+=b;} // needs operator+= && and copy-constructor
/*3*/ array sum(array a, array const& b){return std::move(a)+=b;} // needs operator+= && and can use move-constructor
/*4*/ array sum(array a, array const& b){array tmp(std::move(a)); tmp+=b; return tmp;} // needs operator+= and can use move-constructor

我在https://godbolt.org/z/2YPhcg 中进行了尝试,仅通过计算装配线的数量,所有其他条件都相同可能会告诉你什么是最好的实现。我得到了这些混合的结果:

| code       | gcc -O2     | clang  -O2   |
|:-----------|------------:|:------------:|
| /*1*/      |   33 lines  |     64 lines |
| /*2*/      |   39 lines  |     59 lines |
| /*3*/      |   33 lines  |     62 lines |
| /*4*/      |   33 lines  |     64 lines |

虽然/*3*//*4*/ 可以从sum(std::move(a), b) 甚至sum(sum(a, c), b) 形式的调用中受益。

那么T tmp(a); tmp+=b; return tmp; 仍然是实现operator+(T [const&amp;], T const&amp;) 的最佳方式吗?

看起来如果有一个移动构造函数和一个移动+=,还有其他可能性,但似乎只会在clang中产生更简单的组装。

【问题讨论】:

  • NRVO 不是强制性的
  • 在粗体文本中,您专门询问operator+ 的一个签名,而在前面的文本中,您允许不同的签名,请澄清
  • @M.M 好吧,我对行为感兴趣,而不是签名。

标签: gcc clang operators nrvo


【解决方案1】:

如果签名是:

T operator+(T const& a, T const& b )

(正如您在粗体问题文本中所说),那么正文应该是:

return T(a) += b;

结果对象是唯一构造的TT nrv( lhs ); nrv += rhs; return nrv; 版本理论上存在编译器无法将 nrvresult 对象合并的风险。


请注意,上述签名不允许移出任何参数。如果希望移出 lhs,那么这对我来说似乎是最佳选择:

T operator+(T const& a, T const& b)
{
    return T(a) += b;
}

T operator+(T&& a, T const& b)
{
    return T(std::move(a)) += b;
}

在这两种情况下,结果对象都保证是唯一构造的对象。在采用T a 的“经典”版本中,右值参数的情况会导致额外的移动。

如果您想移出右侧,则可以再添加两个重载 :)

注意,我没有考虑为reasons described here返回T&amp;&amp;的情况

【讨论】:

  • 我不明白与链接问题的关系,你能把它列出来吗?
  • @alfC 我是说我不认为这样做是个好主意T&amp;&amp; operator+(...stuff...)
  • 是的,我不知道我使用左值引用有一段时间了,我仍然不明白返回 T&& 是什么意思。
  • 从技术上讲,如果操作是可交换的,就像+ 应该是一样(除了std::string,使用+ 表示std::string 是一个错误),那么可以实现这个所有组合为T&amp;T const&amp;T&amp;&amp;(9 种组合)的排列组合。我意识到这里还有一个隐藏的假设,这不是通用的,它仅适用于“值类型”,因为T(a) 应该创建一个独立的副本。 (即类似引用的类型表现不佳。)
  • 我编辑为使用T const&amp; 而不是T&amp;;最多只有4个排列。是否搬出 r.h.s.是否有用取决于类的属性。 (例如,对于std::string,移出两个操作数没有任何好处)
猜你喜欢
  • 1970-01-01
  • 2011-03-01
  • 2021-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-15
  • 2020-04-13
相关资源
最近更新 更多