在现代C ++中,关于operator + =的对称operator +?

时间:2019-01-18 03:02:14

标签: gcc clang operators nrvo

我正在阅读有关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;
    }

给出一个具有构造函数,move构造函数和合理的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&], T const&)的最佳方法吗?

如果有一个move构造函数和一个+ =移动对象,似乎还有其他可能,但似乎只用clang产生了更简单的汇编。

1 个答案:

答案 0 :(得分:1)

如果签名是:

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

(如您在黑体字问题中所说),则正文应为:

return T(a) += b;

其中结果对象是唯一构造的T。理论上,版本T nrv( lhs ); nrv += rhs; return nrv;冒着编译器无法将nrv结果对象合并的风险。


请注意,上述签名不允许移出任何参数。如果需要退出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&&的情况