Ответ
Идиома copy-and-swap — это надежный и эффективный паттерн для реализации оператора присваивания копированием (operator=), обеспечивающий строгую гарантию безопасности исключений и автоматическую поддержку move-семантики.
Суть идиомы: Вместо того чтобы копировать данные внутри operator=, мы создаем временную копию аргумента (используя конструктор копирования или перемещения), а затем обмениваем (swap) её внутреннее состояние с состоянием текущего объекта.
Полная реализация для класса, управляющего ресурсом:
#include <algorithm> // для std::swap
#include <utility> // для std::exchange
class Buffer {
private:
int* data_ = nullptr;
size_t size_ = 0;
// Дружественная функция обмена
friend void swap(Buffer& a, Buffer& b) noexcept {
using std::swap;
swap(a.data_, b.data_);
swap(a.size_, b.size_);
}
public:
// Конструкторы, деструктор...
~Buffer() { delete[] data_; }
// Ключевой метод обмена
void swap(Buffer& other) noexcept {
::swap(*this, other); // Вызов дружественной функции
}
// Оператор присваивания по ИДИОМЕ COPY-AND-SWAP
Buffer& operator=(Buffer other) noexcept { // ПАРАМЕТР ПЕРЕДАЕТСЯ ПО ЗНАЧЕНИЮ!
other.swap(*this); // Обменяли состояние с временной копией
return *this;
// Деструктор 'other' (который теперь содержит старые данные *this)
// автоматически освобождает старый ресурс.
}
// Конструктор копирования (для полноты примера)
Buffer(const Buffer& other) : size_(other.size_) {
data_ = new int[size_];
std::copy(other.data_, other.data_ + size_, data_);
}
// Конструктор перемещения генерируется автоматически или может быть =default
};
Преимущества:
- Безопасность исключений: Вся работа, которая может бросить исключение (выделение памяти, копирование), происходит при создании параметра
otherдо изменения состояния текущего объекта. Если это не удастся,*thisостанется неизменным. - Автоматическая поддержка перемещения: Поскольку параметр передается по значению, компилятор сам вызовет конструктор перемещения для r-value аргументов, делая
operator=универсальным. - Устранение дублирования кода: Логика копирования содержится только в конструкторе копирования.
Ответ 18+ 🔞
Да ты посмотри, какая хитрая жопа эти программисты придумали! Сидят, блядь, думают, как бы так сделать, чтобы и безопасно, и красиво, и чтобы ничего не проебать. И родилась эта идиома — copy-and-swap, ёпта!
Суть-то проще пареной репы, если разобраться. Вместо того чтобы внутри оператора присваивания (operator=) городить огород с проверками, копированием и «а вдруг исключение вылетит», мы делаем проще, как дурак.
Мы берём и сразу делаем копию того, что нам передали. Ага, параметр в оператор принимается по значению. Это ключевой момент, запомни, как «Отче наш»!
Buffer& operator=(Buffer other) noexcept { // Смотри сюда! По значению!
other.swap(*this);
return *this;
}
Вот и вся магия. Компилятор, когда видит buffer1 = buffer2, сначала создаёт этот самый временный объект other. Если buffer2 — lvalue, вызовется конструктор копирования. Если rvalue (типа std::move(buffer2)), то вызовется конструктор перемещения. Удобно, блядь? Одна запись, а работает для обоих случаев!
А дальше — разводка лёгкая. У нас же есть метод swap, который честно и без исключений меняет местами все внутренности двух объектов. Меняем состояние свежесозданной копии other с состоянием текущего объекта *this.
И тут наступает момент истины. Временный объект other теперь хранит старые данные *this. Функция заканчивается, other уничтожается, и его деструктор автоматом чистит за нами старый ресурс. Красота, ядрёна вошь!
В чём, собственно, профит, чувак?
- Про безопасность исключений. Вся опасная работа — выделение памяти, копирование байтов — происходит до строки
other.swap(*this). То есть пока мы создаём копию. Если там что-то пошло не так и вылетело исключение — наш текущий объект (*this) даже не тронут, стоит себе как ни в чём не бывало. Гарантия — железная. - Про универсальность. Не нужно городить два оператора (
operator=(const Buffer&)иoperator=(Buffer&&)). Один, ёб твою мать, делает всё. Потому что перемещение или копирование определяется на этапе создания аргументаother. - Про DRY (Don't Repeat Yourself). Логика копирования живёт в одном месте — в конструкторе копирования. В операторе присваивания её не дублируем. Меньше кода — меньше шансов накосячить.
Единственный нюанс, про который иногда спорят — это производительность. Мол, лишнее копирование/перемещение при передаче параметра. Но, во-первых, компиляторы часто это оптимизируют. А во-вторых, надёжность и ясность кода в 99% случаев важнее этой микрооптимизации. Не гонись за призраками, лучше спи спокойно.
В общем, идиома — огонь. Пользуйся, не ошибёшься.