Ответ 1
Это моя вина. (наполовину шучу, полу-не).
Когда я впервые показал пример реализации операторов присваивания переходов, я просто использовал swap. Затем какой-то умный парень (я не помню, кто) указал мне, что побочные эффекты разрушения lhs до назначения могут быть важными (например, unlock() в вашем примере). Поэтому я прекратил использовать swap для назначения перемещения. Но история использования swap все еще существует и задерживается.
В этом примере нет причин использовать swap. Он менее эффективен, чем вы предлагаете. Действительно, в libС++ я делаю именно то, что вы предлагаете:
unique_lock& operator=(unique_lock&& __u)
{
if (__owns_)
__m_->unlock();
__m_ = __u.__m_;
__owns_ = __u.__owns_;
__u.__m_ = nullptr;
__u.__owns_ = false;
return *this;
}
В общем случае оператор присваивания перемещения должен:
- Уничтожьте видимые ресурсы (хотя, возможно, сохраните ресурсы детали реализации).
- Переместить назначение всех баз и членов.
- Если назначение переходов базой и членами не создало ресурса без ресурсов, сделайте так.
Так же:
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;
может ошибиться. Я думаю, что важно рассмотреть все ваши варианты для каждого класса.
Подробно рассмотрим пример OP: std::unique_lock(unique_lock&&)
.
замечания:
а. Этот класс довольно прост. Он имеет два элемента данных:
mutex_type* __m_; bool __owns_;
В. Этот класс находится в библиотеке общего назначения, которая будет использоваться неизвестным числом клиентов. В такой ситуации проблемы с производительностью являются высокоприоритетными. Мы не знаем, будут ли наши клиенты использовать этот класс в критическом для производительности коде или нет. Поэтому мы должны предположить, что они есть.
С. Конструктор перемещения для этого класса будет состоять из небольшого количества загрузок и хранилищ, несмотря ни на что. Таким образом, хороший способ взглянуть на производительность - подсчитать нагрузки и магазины. Например, если вы делаете что-то с 4 магазинами, а кто-то другой делает то же самое только с двумя магазинами, обе ваши реализации очень быстры. Но их дважды так же быстро, как и ваши! Это различие может иметь решающее значение в некоторых узких циклах клиента.
Сначала давайте посчитаем нагрузки и хранилища в конструкторе по умолчанию и в функции замены членов:
// 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% быстрее!
Это классический инженерный компромисс. Существует бесплатный обед. И инженеры никогда не освобождаются от необходимости принимать решения о компромиссах. В ту минуту, когда самолеты начинают падать из воздуха, а атомные станции начинают плавиться.
Для libС++ я выбрал более быстрое решение. Мое логическое обоснование заключается в том, что для этого класса я лучше понимаю его; класс достаточно прост, что мои шансы получить его правильно высоки; и мои клиенты будут оценивать производительность. Я мог бы прийти к другому выводу для другого класса в другом контексте.