Ничего себе, здесь так много всего, что нужно очистить ...
Во-первых, Копировать и поменять не всегда является правильным способом реализации Назначения копирования.Почти наверняка в случае dumb_array
это неоптимальное решение.
Использование Copy and Swap для dumb_array
является классическим примером размещения самых дорогихработа с наиболее полными функциями в нижнем слое.Он идеально подходит для клиентов, которые хотят использовать все возможности и готовы платить за производительность.Они получают именно то, что хотят.
Но это пагубно для клиентов, которые не нуждаются в полной функциональности и вместо этого ищут высочайшую производительность.Для них dumb_array
- это просто еще одна часть программного обеспечения, которую они должны переписать, потому что она слишком медленная.Если бы dumb_array
был разработан по-разному, он мог бы удовлетворить обоих клиентов без каких-либо компромиссов ни с одним из них.
Ключом к удовлетворению обоих клиентов является создание самых быстрых операций на самом низком уровне, а затем добавление APIВдобавок ко всему, для более полных функций при больших затратах.Т.е. вам нужна гарантия сильного исключения, хорошо, вы платите за это.Вам это не нужно?Вот более быстрое решение.
Давайте конкретизируем: вот быстрая базовая гарантия исключения Оператор копирования назначений для dumb_array
:
dumb_array& operator=(const dumb_array& other)
{
if (this != &other)
{
if (mSize != other.mSize)
{
delete [] mArray;
mArray = nullptr;
mArray = other.mSize ? new int[other.mSize] : nullptr;
mSize = other.mSize;
}
std::copy(other.mArray, other.mArray + mSize, mArray);
}
return *this;
}
Объяснение:
Один изболее дорогие вещи, которые вы можете сделать на современном оборудовании, это совершить путешествие в кучу.Все, что вы можете сделать, чтобы избежать поездки в кучу, - это затраченное время и усилия.Клиенты dumb_array
вполне могут захотеть назначить массивы одинакового размера.И когда они это сделают, все, что вам нужно сделать, это memcpy
(скрыто под std::copy
).Вы не хотите выделять новый массив того же размера, а затем освобождать старый массив того же размера!
Теперь для ваших клиентов, которые на самом деле хотят строгой безопасности исключений:
template <class C>
C&
strong_assign(C& lhs, C rhs)
{
swap(lhs, rhs);
return lhs;
}
Или, может быть, если вы хотите использовать преимущество перемещения в C ++ 11, которое должно быть:
template <class C>
C&
strong_assign(C& lhs, C rhs)
{
lhs = std::move(rhs);
return lhs;
}
Если клиенты dumb_array
ценят скорость, они должны вызвать operator=
.Если им нужна строгая безопасность исключений, есть универсальные алгоритмы, которые они могут вызывать, которые будут работать с широким спектром объектов и должны быть реализованы только один раз.
Теперь вернемся к исходному вопросу (который имеет тип-o вэтот момент времени):
Class&
Class::operator=(Class&& rhs)
{
if (this == &rhs) // is this check needed?
{
// ...
}
return *this;
}
Это на самом деле спорный вопрос.Кто-то скажет «да», а кто-то скажет «нет».
Мое личное мнение - нет, эта проверка вам не нужна.
Обоснование:
Когда объект связываетсяк rvalue-ссылке это одна из двух вещей:
- Временный.
- Объект, которому звонящий хочет, чтобы вы поверили, является временным.
Если у вас есть ссылка на объект, который является действительным временным, то по определению у вас есть уникальная ссылка на этот объект.На него нельзя ссылаться нигде во всей вашей программе.Т.е. this == &temporary
невозможно .
Теперь, если ваш клиент обманул вас и пообещал вам, что вы получаете временное пособие, а не так, то это ответственность клиентачтобы быть уверенным, что вам не нужно заботиться.Если вы хотите быть очень осторожным, я считаю, что это будет лучшей реализацией:
Class&
Class::operator=(Class&& other)
{
assert(this != &other);
// ...
return *this;
}
Т.е. если вы передали собственную ссылку, это ошибка со стороныклиент, который должен быть исправлен.
Для полноты ниже приведен оператор назначения перемещения для dumb_array
:
dumb_array& operator=(dumb_array&& other)
{
assert(this != &other);
delete [] mArray;
mSize = other.mSize;
mArray = other.mArray;
other.mSize = 0;
other.mArray = nullptr;
return *this;
}
В типичном случае использования назначения перемещения *this
будетудаленный объект и поэтому delete [] mArray;
должен быть неактивным.Очень важно, чтобы реализации сделали удаление на nullptr как можно быстрее.
Предупреждение:
Некоторые утверждают, что swap(x, x)
- это хорошая идея или просто необходимое зло.И это, если своп переходит к свопу по умолчанию, может привести к самостоятельному назначению.
Я не согласен, что swap(x, x)
когда-либо хорошая идея. Если я найду код в своем собственном коде, я считаю это ошибкой производительности и исправлю ее. Но в случае, если вы хотите разрешить это, поймите, что swap(x, x)
выполняет self-move-assignemnet только для значения move-from. И в нашем dumb_array
примере это будет совершенно безвредно, если мы просто опустим assert или ограничим его случаем с удалением:
dumb_array& operator=(dumb_array&& other)
{
assert(this != &other || mSize == 0);
delete [] mArray;
mSize = other.mSize;
mArray = other.mArray;
other.mSize = 0;
other.mArray = nullptr;
return *this;
}
Если вы самостоятельно назначаете два удаленных (пустых) dumb_array
, вы не делаете ничего неправильного, кроме вставки бесполезных инструкций в вашу программу. Такое же наблюдение можно сделать для подавляющего большинства объектов.
<
Обновление >
Я подумала над этим вопросом и несколько изменила свою позицию. Теперь я считаю, что назначение должно быть терпимым к самостоятельному назначению, но условия публикации для назначения копирования и перемещения отличаются:
Для копирования:
x = y;
необходимо иметь постусловие, что значение y
не должно изменяться. Когда &x == &y
, то это постусловие переводится в: назначение самокопирования не должно влиять на значение x
.
Для задания на перемещение:
x = std::move(y);
нужно иметь постусловие, что y
имеет допустимое, но неопределенное состояние. Когда &x == &y
, то это постусловие переводится в: x
имеет допустимое, но неопределенное состояние. То есть Самостоятельное перемещение не должно быть запретом. Но это не должно разбиться. Это постусловие согласуется с разрешением swap(x, x)
просто работать:
template <class T>
void
swap(T& x, T& y)
{
// assume &x == &y
T tmp(std::move(x));
// x and y now have a valid but unspecified state
x = std::move(y);
// x and y still have a valid but unspecified state
y = std::move(tmp);
// x and y have the value of tmp, which is the value they had on entry
}
Выше работает, пока x = std::move(x)
не падает. Он может оставить x
в любом допустимом, но не указанном состоянии.
Я вижу три способа запрограммировать оператор присваивания для dumb_array
для достижения этой цели:
dumb_array& operator=(dumb_array&& other)
{
delete [] mArray;
// set *this to a valid state before continuing
mSize = 0;
mArray = nullptr;
// *this is now in a valid state, continue with move assignment
mSize = other.mSize;
mArray = other.mArray;
other.mSize = 0;
other.mArray = nullptr;
return *this;
}
Вышеприведенная реализация допускает самопредставление, но *this
и other
в конечном итоге становятся массивом нулевого размера после присваивания самоперемещением, независимо от того, каково первоначальное значение *this
. Это хорошо.
dumb_array& operator=(dumb_array&& other)
{
if (this != &other)
{
delete [] mArray;
mSize = other.mSize;
mArray = other.mArray;
other.mSize = 0;
other.mArray = nullptr;
}
return *this;
}
Вышеприведенная реализация допускает самостоятельное назначение так же, как оператор копирования, делая его неоперативным. Это тоже хорошо.
dumb_array& operator=(dumb_array&& other)
{
swap(other);
return *this;
}
Вышеприведенное допустимо, только если dumb_array
не содержит ресурсов, которые должны быть уничтожены "немедленно". Например, если единственным ресурсом является память, все в порядке. Если dumb_array
может удерживать блокировки мьютекса или открытое состояние файлов, клиент может разумно ожидать, что эти ресурсы в lhs назначения перемещения будут немедленно освобождены, и поэтому эта реализация может быть проблематичной.
Стоимость первого составляет два дополнительных магазина. Стоимость второго - тестовая и отраслевая. Оба работают. Оба соответствуют всем требованиям таблицы 22 MoveAssignable требованиям стандарта C ++ 11. Третий также работает по модулю проблемы ресурсов памяти.
Все три реализации могут иметь разные затраты в зависимости от аппаратного обеспечения: Насколько дорогой является филиал? Много регистров или очень мало?
Вывод состоит в том, что само-перемещение-назначение, в отличие от само-копирования-назначения, не должно сохранять текущее значение.
<
/ Update >
Одна последняя (надеюсь) редакция, вдохновленная комментарием Люка Дантона:
Если вы пишете класс высокого уровня, который напрямую не управляет памятью (но может иметь базы или членов, которые это делают), то лучшая реализация назначения перемещения часто:
Class& operator=(Class&&) = default;
Это переместит назначение каждой базы и каждого участника по очереди, и не будет включать проверку this != &other
. Это обеспечит вам высочайшую производительность и безопасность исключений при условии, что вам не нужно поддерживать инварианты среди ваших баз и участников. Для ваших клиентов, требующих строгих исключений безопасности, направьте их на strong_assign
.