Копировать и Идиома?

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

T& operator = (T other){
using std::swap;
swap(*this, other);
return *this;
}

Однако это требует T быть Swappable. Какой тип автоматически std::is_move_constructible_v<T> && std::is_move_assignable_v<T> == true благодаря std::swap.

У меня вопрос, есть ли недостатки использования «Копировать & Переместить «идиома вместо этого?

T& operator = (T other){
*this = std::move(other);
return *this;
}

при условии, что вы реализуете перемещение-назначение для T потому что, очевидно, в противном случае вы получите бесконечную рекурсию.

Этот вопрос отличается от Должен ли идиома «Копировать и поменять» идиома «Копировать и переместить» в C ++ 11? в том, что этот вопрос носит более общий характер и использует оператор присваивания перемещения вместо фактического перемещения членов вручную. Что позволяет избежать проблем с очисткой, которые предсказывали ответ в связанной ветке.

22

Решение

Способ реализации Копировать & Перемещение должно быть таким, как указал @Raxvan:

T& operator=(const T& other){
*this = T(other);
return *this;
}

но без std::move как T(other) уже является rvalue и clang выдаст предупреждение о пессимизации при использовании std::move Вот.

Когда существует оператор присваивания перемещения, разница между копией & Обмен и копирование & Перемещение зависит от того, использует ли пользователь swap метод, который имеет лучшую безопасность исключений, чем назначение перемещения. Для стандарта std::swap безопасность исключений идентична между копией & Обмен и копирование & Переехать. Я считаю, что в большинстве случаев это будет swap и назначение перемещения будет иметь ту же исключительную безопасность (но не всегда).

Реализация Копии & Перемещение имеет риск, когда, если оператор назначения перемещения отсутствует или имеет неправильную подпись, оператор назначения копирования будет уменьшен до бесконечной рекурсии. Однако хотя бы лязг предупреждает об этом и мимоходом -Werror=infinite-recursion компилятору этот страх можно снять, что, откровенно говоря, мне не понятно, почему это не ошибка по умолчанию, но я отвлекся.

Я провел некоторое тестирование и много царапин на голове, и вот что я узнал:

  1. Если у вас есть оператор присваивания перемещения, «правильный» способ сделать копию & Своп не будет работать из-за звонка operator=(T) быть неоднозначным с operator=(T&&), Как указывал @Raxvan, вам нужно сделать конструкцию копирования внутри тела оператора присваивания копии. Это считается неполноценным, так как не позволяет компилятору выполнить удаление копии, когда оператор вызывается с r-значением. Однако случаи, когда бы примененная копия элизия обрабатывается заданием перемещения теперь так, что точка является спорной.

  2. Мы должны сравнить:

    T& operator=(const T& other){
    using std::swap;
    swap(*this, T(other));
    return *this;
    }
    

    чтобы:

    T& operator=(const T& other){
    *this = T(other);
    return *this;
    }
    

    Если пользователь не использует пользовательский swapто шаблонное std::swap(a,b) используется. Который по существу делает это:

    template<typename T>
    void swap(T& a, T& b){
    T c(std::move(a));
    a = std::move(b);
    b = std::move(c);
    }
    

    Это означает, что исключение безопасности копирования & Своп — это та же исключительная безопасность, что и у более слабой конструкции ходов и назначений ходов. Если пользователь использует пользовательский своп, то, конечно, безопасность исключения определяется этой функцией свопинга.

    В копии & Перемещение, безопасность исключения полностью определяется оператором присваивания перемещения.

    Я полагаю, что рассмотрение производительности здесь является своего рода спором, поскольку оптимизация компилятора, скорее всего, не даст никакой разницы в большинстве случаев. Но я все равно отмечу, что копия и своп выполняют конструкцию копии, конструкцию перемещения и два назначения перемещения по сравнению с копией & Перемещение, которое создает копию и только одно назначение перемещения. Хотя я ожидаю, что компилятор в большинстве случаев будет запускать один и тот же машинный код, конечно, в зависимости от T.

  class T {
public:
T() = default;
T(const std::string& n) : name(n) {}
T(const T& other) = default;

#if 0
// Normal Copy & Swap.
//
// Requires this to be Swappable and copy constructible.
//
// Strong exception safety if `std::is_nothrow_swappable_v<T> == true` or user provided
// swap has strong exception safety. Note that if `std::is_nothrow_move_assignable` and
// `std::is_nothrow_move_constructible` are both true, then `std::is_nothrow_swappable`
// is also true but it does not hold that if either of the above are true that T is not
// nothrow swappable as the user may have provided a specialized swap.
//
// Doesn't work in presence of a move assignment operator as T t1 = std::move(t2) becomes
// ambiguous.
T& operator=(T other) {
using std::swap;
swap(*this, other);
return *this;
}
#endif

#if 0
// Copy & Swap in presence of copy-assignment.
//
// Requries this to be Swappable and copy constructible.
//
// Same exception safety as the normal Copy & Swap.
//
// Usually considered inferor to normal Copy & Swap as the compiler now cannot perform
// copy elision when called with an rvalue. However in the presence of a move assignment
// this is moot as any rvalue will bind to the move-assignment instead.
T& operator=(const T& other) {
using std::swap;

swap(*this, T(other));
return *this;
}
#endif

#if 1
// Copy & Move
//
// Requires move-assignment to be implemented and this to be copy constructible.
//
// Exception safety, same as move assignment operator.
//
// If move assignment is not implemented, the assignment to this in the body
// will bind to this function and an infinite recursion will follow.
T& operator=(const T& other) {
// Clang emits the following if a user or default defined move operator is not present.
// > "warning: all paths through this function will call itself [-Winfinite-recursion]"// I recommend  "-Werror=infinite-recursion" or "-Werror" compiler flags to turn this into an
// error.

// This assert will not protect against missing move-assignment operator.
static_assert(std::is_move_assignable<T>::value, "Must be move assignable!");

// Note that the following will cause clang to emit:
// warning: moving a temporary object prevents copy elision [-Wpessimizing-move]

// *this = std::move(T{other});

// The move doesn't do anything anyway so write it like this;
*this = T(other);
return *this;
}
#endif

#if 1
T& operator=(T&& other) {
// This will cause infinite loop if user defined swap is not defined or findable by ADL
// as the templated std::swap will use move assignment.

// using std::swap;
// swap(*this, other);

name = std::move(other.name);
return *this;
}
#endif

private:
std::string name;
};
8

Другие решения

У меня вопрос, есть ли недостатки использования «Копировать & Переместить «идиома вместо?

Да, если у вас не реализовано перемещение, вы получите переполнение стекаoperator =(T&&),
Если вы хотите реализовать это, вы получите ошибку компилятора (пример здесь):

struct test
{
test() = default;
test(const test &) = default;

test & operator = (test t)
{
(*this) = std::move(t);
return (*this);
}

test & operator = (test &&)
{
return (*this);
}

};

и если вы делаете test a,b; a = b; Вы получаете ошибку:

error: ambiguous overload for 'operator=' (operand types are 'test' and 'std::remove_reference<test&>::type {aka test}')

Один из способов решить эту проблему — использовать конструктор копирования:

test & operator = (const test& t)
{
*this = std::move(test(t));
return *this;
}

Это будет работать, однако, если вы не реализуете назначение перемещения, вы можете не получить ошибку (в зависимости от настроек компилятора). Учитывая человеческую ошибку, вполне возможно, что этот случай может произойти, и вы закончите переполнение стека во время выполнения, что плохо.

3

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector