Это моя вина.(полушутя, наполовину нет).
Когда я впервые показал примеры реализации операторов присваивания перемещения, я просто использовал swap.Затем какой-то умный парень (я не помню, кто) указал мне, что побочные эффекты разрушения lhs до назначения могут быть важными (например, unlock () в вашем примере).Поэтому я перестал использовать своп для назначения ходов.Но история использования свопа все еще существует и продолжается.
Нет причин использовать своп в этом примере.Это менее эффективно, чем вы предлагаете.Действительно, в libc ++ я делаю именно то, что вы предлагаете:
unique_lock& operator=(unique_lock&& __u)
{
if (__owns_)
__m_->unlock();
__m_ = __u.__m_;
__owns_ = __u.__owns_;
__u.__m_ = nullptr;
__u.__owns_ = false;
return *this;
}
В общем случае оператор присваивания перемещения должен:
- Уничтожить видимые ресурсы (хотя, может быть,сохранить ресурсы с подробностями реализации).
- Переместить назначить все базы и элементы.
- Если назначение перемещения баз и членов не сделало ресурс rhs менее доступным, сделайте это так.
Примерно так:
unique_lock& operator=(unique_lock&& __u)
{
// 1. Destroy visible resources
if (__owns_)
__m_->unlock();
// 2. Move assign all bases and members.
__m_ = __u.__m_;
__owns_ = __u.__owns_;
// 3. If the move assignment of bases and members didn't,
// make the rhs resource-less, then make it so.
__u.__m_ = nullptr;
__u.__owns_ = false;
return *this;
}
Обновление
В комментариях есть дополнительный вопрос о том, как обрабатывать конструкторы перемещения.Я начал там отвечать (в комментариях), но ограничения форматирования и длины затрудняют создание четкого ответа.Таким образом, я выкладываю здесь свой ответ.
Вопрос в том, каков наилучший шаблон для создания конструктора перемещения?Делегировать в конструктор по умолчанию, а затем поменять местами?Преимущество этого состоит в уменьшении дублирования кода.
Мой ответ таков: я думаю, что наиболее важный вывод заключается в том, что программисты должны опасаться следования шаблонам без размышлений.Могут быть некоторые классы, в которых реализация конструктора перемещения по умолчанию + swap - это правильный ответ.Класс может быть большим и сложным.A(A&&) = default;
может делать неправильные вещи.Я думаю, что важно рассмотреть все ваши варианты выбора для каждого класса.
Давайте подробно рассмотрим пример ОП: std::unique_lock(unique_lock&&)
.
Наблюдения:
A.Этот класс довольно прост.Он имеет два элемента данных:
mutex_type* __m_;
bool __owns_;
B.Этот класс находится в библиотеке общего назначения, которая будет использоваться неизвестным числом клиентов.В такой ситуации проблемы с производительностью являются высоким приоритетом.Мы не знаем, будут ли наши клиенты использовать этот класс в критичном для производительности коде или нет.Таким образом, мы должны предположить, что они.
C.Конструктор перемещения для этого класса будет состоять из небольшого количества загрузок и хранилищ, несмотря ни на что.Таким образом, хороший способ взглянуть на производительность - это посчитать нагрузки и запасы.Например, если вы делаете что-то с 4 магазинами, а кто-то другой делает то же самое только с 2 магазинами, обе ваши реализации очень быстрые.Но у них это в два раза так же быстро, как у вас!Это различие может иметь решающее значение в узком цикле некоторого клиента.
Сначала давайте посчитаем нагрузки и сохраняем в конструкторе по умолчанию и в функции обмена членами:
// 2 stores
unique_lock()
: __m_(nullptr),
__owns_(false)
{
}
// 4 stores, 4 loads
void swap(unique_lock& __u)
{
std::swap(__m_, __u.__m_);
std::swap(__owns_, __u.__owns_);
}
Теперь давайте реализуем конструктор перемещениядва пути:
// 4 stores, 2 loads
unique_lock(unique_lock&& __u)
: __m_(__u.__m_),
__owns_(__u.__owns_)
{
__u.__m_ = nullptr;
__u.__owns_ = false;
}
// 6 stores, 4 loads
unique_lock(unique_lock&& __u)
: unique_lock()
{
swap(__u);
}
Первый способ выглядит намного сложнее, чем второй.И исходный код больше и несколько дублирует код, который мы, возможно, уже написали в другом месте (скажем, в операторе присваивания перемещения).Это означает, что у ошибок больше шансов.
Второй способ проще и использует код, который мы уже написали.Таким образом меньше вероятность ошибок.
Первый способ быстрее.Если стоимость грузов и магазинов примерно одинакова, возможно, на 66% быстрее!
Это классический инженерный компромисс.Там нет бесплатного обеда.И инженеры никогда не освобождаются от необходимости принимать решения о компромиссах.В одну минуту самолеты начинают выпадать из воздуха, а атомные станции начинают таять.
Для libc ++ я выбрал более быстрое решение. Мое обоснование состоит в том, что для этого класса я лучше пойму это правильно, несмотря ни на что; класс достаточно прост, поэтому мои шансы сделать все правильно; и мои клиенты будут ценить производительность. Я мог бы прийти к другому выводу для другого класса в другом контексте.