Семантика перемещения сплошь и рядом используется в библиотеке Thread Library — и в случаях, когда копирование не имеет смысла, но сами ресурсы можно передавать, и как оптимизация, чтобы избежать дорогостоящего копирования, когда исходный объект все равно будет уничтожен. Один пример мы видели в разделе 2.2, где std::move()
использовалась для передачи экземпляра std::unique_ptr<>
новому потоку, а второй — в разделе 2.3, когда рассматривали передачу владения потоком от одного объекта std::thread
другому.
Ни один из классов std::thread
, std::unique_lock<>
, std::future<>
, std::promise<>
, std::packaged_task<>
не допускает копирования, но в каждом из них имеется перемещающий конструктор, который позволяет передавать ассоциированный ресурс другому экземпляру и возвращать объекты этих классов из функций. Объекты классов std::string
и std::vector<>
можно копировать, как и раньше, но дополнительно они обзавелись перемещающими конструкторами и перемещающими операторами присваивания, чтобы избежать копирования данных из r -значений.
Стандартная библиотека С++ никогда не делает ничего с объектом, который был явно перемещён в другой объект, кроме его уничтожения или присваивания ему значения (путем копирования или, что более вероятно, перемещения). Однако рекомендуется учитывать в инвариантах класса состояние перемещен-из. Например, экземпляр std::thread
, содержимое которого перемещено, эквивалентен объекту std::thread
, сконструированному по умолчанию, а экземпляр std::string
, бывший источником перемещения, все же находится в согласованном состоянии, хотя не дается никаких гарантий относительно того, что это за состояние (в терминах длины строки или содержащихся в ней символов).
А.1.2. Ссылки на r -значения и шаблоны функций
Еще один нюанс имеет отношение к использованию ссылок на r -значения в качестве параметров шаблона функции: если параметр функции — ссылка на r -значение типа параметра шаблона, механизм автоматического выведения типа аргумента шаблона заключает, что тип — это ссылка на l -значение, если функции передано l -значение, или обычный не-ссылочный тип, если передано r -значение. Фраза получилась довольно запутанной, поэтому приведём пример. Рассмотрим такую функцию:
template
void foo(T&& t) {}
Если при вызове передать ей r -значение, как показано ниже, то в качестве T
выводится тип этого значения:
foo(42);
foo(3.14159);
fоо(std::string());
Но если вызвать foo
, передав l-значение , то механизм выведения типа решит, что T
— ссылка на l -значение:
int i = 42;
foo(i);
Поскольку объявлено, что параметр функции имеет тип T&&
, то получается, что это ссылка на ссылку, и такая конструкция трактуется как обычная одинарная ссылка. Таким образом, сигнатура функции foo()
такова:
void foo(int& t);
Это позволяет одному шаблону функции принимать параметры, являющиеся как l -, так и r -значениями. В частности, это используется в конструкторе std::thread
(см. разделы 2.1 и 2.2), чтобы в случае, когда переданный допускающий вызов объект является r -значением, его можно было бы не копировать, а переместить во внутреннюю память.
Иногда операция копирования класса лишена смысла. Типичный пример — std::mutex
. Действительно, что должно было бы получиться в результате копирования мьютекса? Другой пример — std::unique_lock<>
, экземпляр этого класса является единственным владельцем удерживаемой им блокировки. Честное копирование в этом случае означало бы, что у блокировки два владельца, а это противоречит определению. Передача владения, описанная в разделе А.1.2, имеет смысл, но это не копирование. Уверен, вы назовете и другие примеры.
Стандартная идиома предотвращения копирования класса хорошо известна — объявить копирующий конструктор и копирующий оператор присваивания закрытыми и не предоставлять их реализации. Если теперь какой-нибудь внешний по отношению к классу код попытается скопировать объект такого класса, то произойдёт ошибка на этапе компиляции, а если то же самое попытается сделать член класса или его друг, — то ошибка на этапе компоновки (так как реализации отсутствуют):
class no_copies {
public:
no_copies(){}
private:
no_copies(no_copies const&); ←
Реализаций нет
Читать дальше