Выбрать главу

Сначала создастся новый поток (1) и связывается с объектом t1. Затем владение явно передается объекту t2 в момент его конструирования путем вызова std::move() (2). В этот момент с t1 уже не связан никакой поток выполнения: поток, в котором исполняется функция some_function, теперь связан с t2.

Далее создается еще один поток, который связывается с временным объектом типа std::thread (3). Для последующей передачи владения объекту t1 уже не требуется явный вызов std::move(), так как владельцем является временный объект, а передача владения от временных объектов производится автоматически и неявно.

Объект t3 конструируется по умолчанию (4), а это означает, что в момент создания с ним не связывается никакой поток. Владение потоком, который в данный момент связан с t2, передастся объекту t3 (5), опять-таки путем явного обращения к std::move(), поскольку t2 — именованный объект. После всех этих перемещений t1 оказывается связан с потоком, исполняющим функцию some_other_function, t2 не связан ни с каким потоком, a t3 связан с потоком, исполняющим функцию some_function .

Последнее перемещение (6) передает владение потоком, исполняющим some_function, обратно объекту t1, в котором исполнение этой функции началось. Однако теперь с t1 уже связан поток (который исполнял функцию some_other_function), поэтому вызывается std::terminate(), и программа завершается. Так делается ради совместимости с поведением деструктора std::thread. В разделе 2.1.1 мы видели, что нужно либо явно ждать завершения потока, либо отсоединить его до момента уничтожения; то же самое относится и к присваиванию: нельзя просто «прихлопнуть» поток, присвоив новое значение объекту std::thread, который им управляет.

Поддержка операции перемещения в классе std::thread означает, что владение можно легко передать при возврате из функции, как показано в листинге 2.5.

Листинг 2.5. Возврат объекта std::thread из функции

std::thread f() {

 void some_function();

 return std::thread(some_function);

}

std::thread g() {

 void some_other_function(int);

 std::thread t(some_other_function, 42);

 return t;

}

Аналогично, если требуется передать владение внутрь функции, то достаточно, чтобы она принимала экземпляр std::thread по значению в качестве одного из параметров, например:

void f(std::thread t);

void g() {

 void some_function();

 f(std::thread(some_function));

 std::thread t(some_function);

 f(std::move(t));

}

Одно из преимуществ, которые даёт поддержка перемещения в классе std::thread, заключается в том, что мы можем модифицировать класс thread_guard из листинга 2.3, так чтобы он принимал владение потоком. Это позволит избежать неприятностей в случае, когда время жизни объекта thread_guard оказывает больше, чем время жизни потока, на который он ссылается, а, кроме того, это означает, что никто другой не сможет присоединиться к потоку или отсоединить его, так как владение было передано объекту thread_guard. Поскольку основное назначение этого класса гарантировать завершение потока до выхода из области видимости, я назвал его scoped_thread. Реализация и простой пример использования приведены в листинге 2.6.

Листинг 2.6. Класс scoped_thread и пример его использования

class scoped_thread {

 std::thread t;

public:

 explicit scoped_thread(std::thread t_) : ← (1)

 t(std::move(t_)) {

 if (!t.joinable()) ← (2)

  throw std::logic_error("No thread");

 }

 ~scoped_thread() {

  t.join();         ← (3)

 }

 scoped_thread(scoped_thread const&)=delete;

 scoped_thread& operator=(scoped_thread const&)=delete;

};

struct func; ← см. листинг 2.1

void f() {

 int some_local_state;

 scoped_thread t(std::thread(func(some_local_state))); ← (4)