А.2. Удаленные функции
Иногда операция копирования класса лишена смысла. Типичный пример — std::mutex. Действительно, что должно было бы получиться в результате копирования мьютекса? Другой пример — std::unique_lock<>, экземпляр этого класса является единственным владельцем удерживаемой им блокировки. Честное копирование в этом случае означало бы, что у блокировки два владельца, а это противоречит определению. Передача владения, описанная в разделе А.1.2, имеет смысл, но это не копирование. Уверен, вы назовете и другие примеры.
Стандартная идиома предотвращения копирования класса хорошо известна — объявить копирующий конструктор и копирующий оператор присваивания закрытыми и не предоставлять их реализации. Если теперь какой-нибудь внешний по отношению к классу код попытается скопировать объект такого класса, то произойдёт ошибка на этапе компиляции, а если то же самое попытается сделать член класса или его друг, — то ошибка на этапе компоновки (так как реализации отсутствуют):
class no_copies {
public:
no_copies(){}
private:
no_copies(no_copies const&); ← Реализаций нет
no_copies& operator=(no_copies const&);
};
no_copies a; ← He компилируется
no_copies b(a);
Комитет, разрабатывавший стандарт C++11, конечно, знал об этой идиоме, но счел ее не совсем честным приёмом. Поэтому было решено предоставить более общий механизм, применимый и к другим случаям: объявить функцию удаленной, включив в ее объявление конструкцию = delete. Тогда класс no_copies можно записать в виде:
class no_copies {
public:
no_copies() {}
no_copies(no_copies const&) = delete;
no_copies& operator=(no_copies const&) = delete;
};
Это гораздо нагляднее и четко выражает намерения автора. Кроме того, компилятор может в этом случае выдать более понятное сообщение об ошибке, и к тому же при попытке скопировать объект внутри функции-члена класса ошибка произойдёт уже на этапе компиляции, а не компоновки.
Если, удалив копирующие конструктор и оператор присваивания, вы явно напишете перемещающие конструктор и оператор присваивания, то класс будет допускать только перемещение — как, например, std::thread и std::unique_lock<>. В следующем листинге приведен пример такого класса.
Листинг А.2. Простой тип, допускающий только перемещение
class move_only {
std::unique_ptr<my_class> data;
public:
move_only(const move_only&) = delete;
move_only(move_only&& other):
data(std::move(other.data)) {}
move_only& operator=(const move_only&) = delete;
move_only& operator=(move_only&& other) {
data = std::move(other.data);
return *this;
}
};
move_only m1; │ Ошибка, копирующий конструктор объявлен
move_only m2(m1);←┘ удаленным
move_only m3(std::move(m1));←┐ правильно, имеется переме-
│ щающий конструктор
Объекты, допускающие только перемещение, можно передавать функциям в качестве параметров и возвращать из функций, но если вы захотите переместить содержимое l-значения, то должны будете выразить свое намерение явно, воспользовавшись функцией std::move() или оператором static_cast<T&&>.
Спецификатор = delete можно задать для любой функции, а не только для копирующего конструктора и оператора присваивания. Тем самым вы ясно даете понять, что функция отсутствует. Но это еще не все — удаленная функция участвует в разрешении перегрузки, как любая другая, и вызывает ошибку компиляции, только если будет выбрана. Этим можно воспользоваться для исключения некоторых перегруженных вариантов. Например, если функция принимает параметр типа short, то сужение типа int можно предотвратить, написав перегруженный вариант, который принимает int, и объявив его удаленным:
void foo(short);
void foo(int) = delete;
Любую попытку вызвать foo с параметром типа int компилятор встретит в штыки, так что вызывающей программе придётся явно привести параметр к типу short:
foo(42); ← Ошибка, перегрузка для int удалена
foo((short)42); ← Правильно