Как защитить объект от копирования?
В C++ можно защитить объект от копирования, запретив использование копирующего конструктора и оператора копирующего присваивания. Это важно, когда объект содержит ресурсы (например, дескрипторы, сокеты, указатели на динамическую память), которые не должны копироваться, или когда логика программы подразумевает уникальность экземпляра (например, синглтон или RAII-классы).
🔐 Как запретить копирование объекта в C++
Существует несколько способов, которые зависят от стандарта языка:
📘 Вариант 1: C++11 и выше (рекомендуемый способ)
В C++11 можно явно удалить (delete) копирующий конструктор и оператор присваивания:
class MyClass {
public:
MyClass() = default;
MyClass(const MyClass&) = delete; // запрет копирования
MyClass& operator=(const MyClass&) = delete; // запрет копирования
};
Попытка копирования вызовет ошибку на этапе компиляции:
MyClass a;
MyClass b = a; // ❌ ошибка компиляции
b = a; // ❌ ошибка компиляции
📙 Вариант 2: До C++11 (ручная реализация в private-секции)
До появления = delete нужно было объявлять копирующий конструктор и оператор присваивания в private-секции без реализации:
class MyClass {
public:
MyClass() {}
private:
MyClass(const MyClass&); // недоступен снаружи
MyClass& operator=(const MyClass&); // недоступен снаружи
};
Если кто-то попытается копировать такой объект, компилятор выдаст ошибку о невозможности доступа к приватному элементу.
🔄 А как насчёт перемещения?
Если вы запретили копирование, но хотите разрешить перемещение (move-семантику), вы можете так:
class MyClass {
public:
MyClass() = default;
// запрет копирования
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
// разрешено перемещение
MyClass(MyClass&&) = default;
MyClass& operator=(MyClass&&) = default;
};
Такой класс нельзя копировать, но можно переместить: это удобно, когда вы хотите передавать ресурсы из одного объекта в другой, не создавая копий.
🧱 Наследование от специального базового класса
Для удобства и предотвращения дублирования кода можно создать базовый класс, от которого будут наследоваться некопируемые типы:
class NonCopyable {
protected:
NonCopyable() = default;
~NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
class MyClass : private NonCopyable {
// теперь MyClass некопируемый
};
Это удобно, если в проекте много классов, которые должны быть некопируемыми.
📌 Примеры применения
std::unique_ptr — уникальный умный указатель, запрещает копирование.
<br/>std::unique_ptr<int> a = std::make_unique<int>(5);
std::unique_ptr<int> b = a; // ❌ ошибка компиляции
Синглтон — класс, который должен иметь единственный экземпляр:
<br/>class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
};
- RAII-объекты — управляют ресурсами (файл, память, сокет), где важно, чтобы один объект владел ресурсом.
📉 Почему важно запрещать копирование
-
Риск двойного освобождения памяти — если объект содержит указатель и копируется без глубокого копирования.
-
Логическая ошибка — некоторые объекты, например, логгеры, должны быть только в одном экземпляре.
-
Семантика перемещения — позволяет точно контролировать, кто владеет ресурсом в каждый момент времени.
📚 Отношение к Rule of Five
В C++11 действует правило пяти (Rule of Five): если вы определили один из следующих методов, лучше определить все пять:
-
Деструктор
-
Копирующий конструктор
-
Копирующий оператор присваивания
-
Перемещающий конструктор
-
Перемещающий оператор присваивания
Если вы удалили копирующий конструктор и оператор, вам стоит явно указать, что перемещение допустимо или тоже запрещено.
Запрет на копирование объекта — это важный инструмент в C++ для управления ресурсами и обеспечения корректной логики программ.