Симметричный оператор + в терминах оператора + = в современном C ++? - PullRequest
0 голосов
/ 18 января 2019

Я читал эту заметку о реализации симметричных операторов в Boost.Operator https://www.boost.org/doc/libs/1_69_0/libs/utility/operators.htm#symmetry, и я подозреваю, что она ужасно устарела.

Речь идет о том, как лучше всего реализовать operator+ в общем случае, если имеется согласованный operator+=. Отсюда вывод: (100)

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&], T const&)?

Похоже, что если есть конструктор перемещения и перемещение + =, есть и другие возможности, но кажется, что они создают более простую сборку в clang.

1 Ответ

0 голосов
/ 05 февраля 2019

Если подпись:

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, случай аргумента rvalue повлечет за собой дополнительный ход.

Если вы хотите выйти из правой части, то можно добавить еще две перегрузки:)

Обратите внимание, что я не рассматривал случай возврата T&& по причинам, описанным здесь

...