T& operator=(const T&); // классический вид
T& operator=(T); // потенциально оптимизированный
// вид (см. рекомендацию 27)
Второй вариант имеет смысл использовать, если вам в любом случае требуется копия аргумента в теле вашего оператора, как, например, при использовании идиомы, основанной на использовании функции обмена (см. рекомендацию 56).
Избегайте делать любой оператор присваивания виртуальным (см. [Meyers96] §33 и [Sutter04] §19). Если вы полагаете, что вам требуется виртуальное поведение присваивания, обратитесь сначала к указанной литературе. Если и после этого вы стоите на своем, то лучше использовать виртуальную именованную функцию, а не оператор (например, virtual void Assign(const T&);
).
He возвращайте const T&
. Хотя этот тип возвращаемого значения имеет то преимущество, что защищает от странных присваиваний наподобие (a=b)=c
, главным его недостатком является то, что вы не сможете поместить объекты типа T
в контейнеры стандартной библиотеки; эти контейнеры требуют, чтобы оператор присваивания возвращал тип T&
.
Всегда делайте копирующее присваивание безопасным в смысле исключений, причем предпочтительна строгая гарантия (см. рекомендацию 71).
Убедитесь, что ваш оператор присваивания безопасен в смысле присваивания самому себе. Избегайте написания оператора копирующего присваивания, который для корректной работы полагается на проверку присваивания самому себе; зачастую это говорит о недостаточной безопасности в смысле исключений. Если вы пишете копирующее присваивание с использованием идиомы обмена (см. рекомендацию 56), то вы автоматически обеспечиваете как строгую безопасность в смысле исключений, так и безопасность в смысле присваивания самому себе. Если присваивание самому себе часто встречается в программе из-за использования ссылочных синонимов или по каким-то иным причинам, проверка присваивания самому себе может использоваться в качестве средства оптимизации во избежание лишней работы.
Явно вызывайте все операторы присваивания базовых классов и всех данных-членов ([Meyers97] §16); обратите внимание, что идиома обмена автоматически заботится обо всех этих вещах. Возвращайте из оператора присваивания значение *this
([Meyers97] §15).
Ссылки
[Alexandrescu03a] • [Cargill92] pp. 41-42, 95 • [Cline99] §24.01-12 • [Koenig97] §4 • [Meyers96] §33 • [Meyers97] §17 • [Murray93] §2.2.1 • [Stroustrup00] §10.4.4.1, §10.4.6.3 • [Sutter00] §13, §38, §41 • [Sutter04] §19
56. Обеспечьте бессбойную функцию обмена
Резюме
Обычно имеет смысл предоставить для класса функцию swap
в целях эффективного и бессбойного обмена внутреннего содержимого объекта с внутренним содержимым другого объекта. Такая функция может пригодиться для реализации ряда идиом, от простого перемещения объектов до реализации присваивания, легко обеспечивающего функцию принятия результатов работы со строгими гарантиями безопасности для вызывающего кода (см. также рекомендацию 51).
Обсуждение
Обычно функция swap
выглядит примерно следующим образом (здесь U
— некоторый пользовательский тип):
class T { // ...
public:
void swap(T& rhs) {
member1_.swap(rhs.member1_);
std::swap(member2_, rhs.member2_);
}
private:
U member1_;
int member2_;
};
Для примитивных типов и стандартных контейнеров можно использовать std::swap
. Другие классы могут реализовывать обмен в виде функций-членов с различными именами.
Рассмотрим использование swap
для реализации копирующего присваивания посредством копирующего конструктора. Приведенная далее реализация оператора operator= обеспечивает строгую гарантию (см. рекомендацию 71), хотя и ценой создания дополнительного объекта, что может оказаться неприемлемым, если имеется более эффективный способ выполнения безопасного присваивания объектов типа T:
T& T::operator=(const T& other) { // Вариант 1 (традиционный)
T temp(other);
swap(temp);
return *this;
}
T& T::operator=(T temp) { // Вариант 2 (см. рекомендацию 27)
swap(temp); // Обратите внимание на передачу
return *this; // temp по значению
}
Но что если тип U
не имеет бессбойной функции обмена, как в случае многих существующих классов, но вам требуется поддержка функции обмена для типа T
? Не все потеряно.
• Если копирующий конструктор и оператор копирующего присваивания U
не дают сбоев, то с объектами типа U
вполне справится std::swap
.
Читать дальше