Еще один нюанс имеет отношение к использованию ссылок на
template
void foo(T&& t) {}
Если при вызове передать ей T
выводится тип этого значения:
foo(42);
foo(3.14159);
fоо(std::string());
Но если вызвать foo
, передав T
— ссылка на
int i = 42;
foo(i);
Поскольку объявлено, что параметр функции имеет тип T&&
, то получается, что это ссылка на ссылку, и такая конструкция трактуется как обычная одинарная ссылка. Таким образом, сигнатура функции foo
такова:
void foo
Это позволяет одному шаблону функции принимать параметры, являющиеся как std::thread
(см. разделы 2.1 и 2.2), чтобы в случае, когда переданный допускающий вызов объект является
А.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
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));←┐
правильно, имеется переме-
│
щающий конструктор