Как написать оператор присваивания
Вопрос
Как правильно реализовать копирующее присваивание для класса, владеющего буфером? Какие проблемы бывают у наивной реализации?
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Самоприсваивание: удаление своего буфера до копирования
Исключение из new оставляет объект в разрушенном состоянии
Copy-and-swap: строгая гарантия, одна реализация для copy и move
Цена: лишняя аллокация, даже если ёмкости хватает
Альтернатива: переиспользовать буфер при достаточной ёмкости, с базовой гарантией
Возвращать *this по ссылке
Что могут спросить следом
- Почему стандартный vector не использует copy-and-swap в operator=?
- Как реализовать swap, чтобы его находил ADL?
Разбор
Наивный вариант «освободить своё, выделить новое, скопировать» имеет две проблемы. При a = a мы удаляем буфер, который потом читаем как источник. А если new бросит bad_alloc, объект останется с висячим указателем и сломает деструктор. Надёжная классика: copy-and-swap. Оператор принимает аргумент по значению, копия строится до входа в функцию, и если она бросит, наш объект не тронут. Внутри только noexcept-обмен указателей и размеров, а старый буфер уйдёт в деструктор временного объекта. Самоприсваивание тоже работает. Плюс один оператор обслуживает и rvalue: аргумент сконструируется перемещением. Цена в том, что мы всегда выделяем новую память, даже если наш буфер достаточно большой. Поэтому std::vector сам так не делает: он переиспользует ёмкость и даёт базовую гарантию. Для своих классов я по умолчанию беру правило нуля, а если пишу вручную, то copy-and-swap, если производительность присваивания не критична.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.
Ещё по теме «Правило 0/3/5»
- Длинная арифметика: класс BigUint и факториал
- Класс матрицы 2×2 и быстрое возведение в степень
- Пустой деструктор и перемещение
- Когда не генерируется перемещение
- Что объявили и что сгенерирует компилятор
- Объявили перемещающий конструктор
- Почему move должен быть noexcept
- Что выведет: деструктор отключил перемещение