Сделайте копирующее присваивание не-`virtual`, принимайте параметр `const&` и возвращайте не-`const&`
Причина
Это просто и эффективно. Если вы хотите оптимизировать для правых значений, предоставьте перегрузку, которая принимает && (см. F.18).
Пример
class Foo {
public:
Foo& operator=(const Foo& x)
{
// ХОРОШО: не нужно проверять самоприсваивание (кроме производительности)
auto tmp = x;
swap(tmp); // см. C.83
return *this;
}
// ...
};
Foo a;
Foo b;
Foo f();
a = b; // присвоить левое значение: копировать
a = f(); // присвоить правое значение: потенциально переместить
Примечание
Техника реализации swap предоставляет сильную гарантию.
Пример
Но что, если вы можете получить значительно лучшую производительность, не создавая временную копию? Рассмотрим простой Vector, предназначенный для домена, где присваивание больших Vectorов одинакового размера является обычным. В этом случае копирование элементов, подразумеваемое техникой реализации swap, может привести к увеличению стоимости на порядок величины:
template<typename T>
class Vector {
public:
Vector& operator=(const Vector&);
// ...
private:
T* elem;
int sz;
};
Vector& Vector::operator=(const Vector& a)
{
if (a.sz > sz) {
// ... используйте технику swap, её нельзя улучшить ...
return *this;
}
// ... копировать sz элементов из *a.elem в elem ...
if (a.sz < sz) {
// ... уничтожить лишние элементы в *this и отрегулировать размер ...
}
return *this;
}
Письмо непосредственно к целевым элементам даст вам только базовую гарантию, а не сильную гарантию, предоставленную техникой swap. Остерегайтесь самоприсваивания.
Альтернативы: Если вы думаете, что вам нужен оператор virtual присваивания, и понимаете, почему это глубоко проблематично, не называйте его operator=. Создайте именованную функцию, подобную virtual void assign(const Foo&). См. конструктор копирования против clone().
Применение
Посмотрите на деструктор, чтобы определить, имеет ли тип семантику указателя или семантику значения.
- (Простое) Оператор присваивания не должен быть виртуальным. Здесь дракон!
- (Простое) Оператор присваивания должен возвращать
T&для включения цепочки, а не альтернативы, подобныеconst T&, которые вмешиваются в композиционность и помещение объектов в контейнеры. - (Умеренное) Оператор присваивания должен (неявно или явно) вызывать все операторы присваивания базового класса и членов.