Исправление к вопросу
Способ реализации Copy & Move должен быть таким, как указал @Raxvan:
T& operator=(const T& other){
*this = T(other);
return *this;
}
но без std::move
, поскольку T(other)
уже является значением r, и clang выдаст предупреждение о пессимизации при использовании здесь std::move
.
Резюме
Когда существует оператор присваивания перемещения, разница между копированием и заменой и копированием и перемещением зависит от того, использует ли пользователь метод swap
, который обеспечивает лучшую безопасность исключений, чем присваивание перемещения. Для стандартного std::swap
безопасность исключений одинакова для копирования и замены и копирования и перемещения. Я считаю, что в большинстве случаев swap
и назначение перемещения будут иметь одинаковую безопасность исключений (но не всегда).
Реализация копирования и перемещения сопряжена с риском, когда, если оператор присваивания перемещения отсутствует или имеет неправильную подпись, оператор присваивания копирования сведется к бесконечной рекурсии. Однако, по крайней мере, clang предупреждает об этом, и, передав -Werror=infinite-recursion
компилятору, этот страх можно снять, что, честно говоря, выше моего понимания, почему это не ошибка по умолчанию, но я отвлекся.
Мотивация
Я провел несколько тестов и много почесал голову, и вот что я узнал:
Если у вас есть оператор присваивания перемещения, «правильный» способ копирования и замены не будет работать из-за того, что вызов operator=(T)
неоднозначен с operator=(T&&)
. Как указал @Raxvan, вам нужно выполнить конструкцию копирования внутри тела оператора присваивания копии. Это считается неполноценным, так как не позволяет компилятору выполнять исключение копирования, когда оператор вызывается с rvalue. Однако случаи, когда было бы применено исключение копирования, теперь обрабатываются назначением перемещения, так что этот вопрос является спорным.
Мы должны сравнить:
T& operator=(const T& other){
using std::swap;
swap(*this, T(other));
return *this;
}
to:
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);
}
Это означает, что безопасность исключений Copy & Swap такая же безопасность исключений, как более слабая конструкция перемещения и назначение перемещения. Если пользователь использует пользовательский своп, то, конечно, безопасность исключений диктуется этой функцией свопа.
В Copy & Move безопасность исключений полностью определяется оператором присваивания перемещения.
Я считаю, что смотреть на производительность здесь довольно спорно, поскольку оптимизация компилятора, скорее всего, не будет иметь значения в большинстве случаев. Но я все равно замечу, что копирование и обмен выполняют построение копирования, построение перемещения и два назначения перемещения, по сравнению с копированием и перемещением, которое выполняет построение копирования и только одно назначение перемещения. Хотя я ожидаю, что компилятор в большинстве случаев выдаст один и тот же машинный код, конечно, в зависимости от 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;
};
person
Emily L.
schedule
12.04.2017
operator =
с новым размещением:T& operator =(const T & other) { this->~T(); return * new(this) T(other); }
- person Raxvan   schedule 12.04.2017swap
также выдает исключение, поскольку он использует конструктор перемещения. Специализированныйswap
может избежать броска, что является хорошим моментом. - person Emily L.   schedule 12.04.2017T
, поскольку его время жизни заканчивается в~T()
. - person Maxim Egorushkin   schedule 12.04.2017T & operator (T&& other)
- person Raxvan   schedule 12.04.2017