оператор+ и семантика перемещения

Как правильно реализовать семантику перемещения с помощью operator+? Аналогично тому, как это работает для std::string?

Я попытался сделать следующее, однако я надеялся, что есть более элегантный и, возможно, более правильный способ сделать это:

class path
{
    std::vector<std::string> path_;
public:

    path& path::operator+=(const path& other)
    {
        path_.insert(std::begin(path_), std::begin(other.path_), std::end(other.path_));
        return *this;
    }

    path& path::operator+=(path&& other)
    {
        path_.insert(std::begin(path_), std::make_move_iterator(std::begin(other.path_)), std::make_move_iterator(std::end(other.path_)));
        return *this;
    }
};

template<typename L, typename R>
typename std::enable_if<std::is_convertible<path, L>::value, path>::type operator+(const L& lhs, const R& rhs)
{
    auto tmp = std::forward<L>(lhs);
    tmp     += std::forward<R>(rhs);
    return tmp;
}

person ronag    schedule 31.12.2011    source источник


Ответы (1)


Слишком сложно. :) Просто соблюдайте правило, которому вы уже должны были следовать:

  • Возьмите левую часть operator+ по значению
  • реализовать operator+ с точки зрения operator+= на левой стороне

Это уже было верно в C++03 из-за исключения копирования и RVO. Эмпирическое правило: если вы все равно делаете копию, сделайте это в параметрах.

С этим в мыслях:

#include <iterator>
#include <utility>

class path
{
    std::vector<std::string> path_;
public:

    path& operator+=(path other)
    {
        auto op_begin = std::make_move_iterator(std::begin(other.path_));
        auto op_end = std::make_move_iterator(std::end(other.path_));
        path_.reserve(path_.size() + other.path_.size());
        path_.insert(std::end(path_), op_begin, op_end);
        return *this;
    }
};

path operator+(path lhs, path rhs)
{
  return std::move(lhs += std::move(rhs));
}

Это должна быть самая оптимальная форма. Обратите внимание, что я также изменил ваш operator+=, чтобы он фактически добавлял путь, а не начинал его (надеюсь, это то, что вы имели в виду. Если нет, не стесняйтесь снова изменить его на std::begin(path_)).

Я также сделал правую часть значений operator+ и operator+=, а затем просто передвинул их. std::make_move_iterator тоже хорошая утилита. Как следует из названия, вместо копирования он перемещает указанные элементы. Это действительно должно быть настолько быстро, насколько это возможно.

Другая версия может заключаться в использовании версии итератора std::move в operator+=:

path& operator+=(path other)
{
    path_.reserve(path_.size() + other.path_.size());
    std::move(other.begin(), other.end(), std::back_inserter(path_));
    return *this;
}
person Xeo    schedule 31.12.2011
comment
Очевидно, это не самая оптимальная форма. Если LHS является lvalue, вы просто избыточно скопировали его. - person Puppy; 31.12.2011
comment
@DeadMG: Эм... operator+ все равно возвращает новую копию. Где избыточная копия, которую я не вижу? - person Xeo; 31.12.2011
comment
Нет ли дополнительных случаев, когда lhs или rhs является значением x и может использоваться повторно? (просто идея) - person Kos; 31.12.2011
comment
@Kos: Если они есть, они уже будут перемещены в параметры по значению. Я не вижу, как еще вы могли бы использовать их повторно. - person Xeo; 31.12.2011
comment
@Xeo: в LHS оператора. И действительно, в RHS оператора. - person Puppy; 31.12.2011