C++. Сборник рецептов.

12.5. Передача аргумента функции потока.

Проблема.

Требуется передать аргумент в вашу функцию потока, однако средствами библиотеки Boost Threads предусматривается передача только функторов без аргументов.

Решение.

Создайте адаптер функтора, который принимает ваши параметры и возвращает функтор без параметров. Адаптер функтора можно использовать там, где должен был бы быть функтор потока. Пример 12.6 показывает, как это можно сделать.

Пример 12.6. Передача аргументов функции потока.

#include <iostream>

#include <string>

#include <functional>

#include <boost/thread/thread.hpp>

// typedef используется для того, чтобы приводимые ниже объявления лучше.

// читались.

Typedef void (*WorkerFunPtr)(const std::string&);

Template<typename FunT, // Тип вызываемой функции.

 typename ParamT>       // тип ее параметра.

Struct Adapter {

 Adapter(FunT f, ParamT& p) : // Сконструировать данный адаптер и.

  f_(f), p_(&p) {}            // установить члены на значение функции и ее.

                              // аргумента.

 void operator()() { // Просто вызов функции с ее аргументом.

  f_(*p_);

 }

Private:

 FunT f_;

 ParamT* p_; // Использовать адрес параметра. чтобы избежать лишнего.

             // копирования.

};

Void worker(const std::string& s) {

 std::cout << s << '\n';

}

Int main() {

 std::string s1 = "This is the first thread!";

 std::string s2 = "This is the second thread!";

 boost::thread thr1(Adapter<WorkerFunPtr, std::string>(worker, s1));

 boost::thread thr2(Adapter<WorkerFunPtr, std::string>(worker, s2));

 thr1.join();

 thr2.join();

}

Обсуждение.

Здесь приходится решать принципиальную проблему, причем характерную не только для потоков или проекта Boost, а общую проблему, возникающую при необходимости передачи функтора с одной сигнатурой туда, где требуется другая сигнатура. Решение состоит в создании адаптера.

Синтаксис может показаться немного путаным, но фактически в примере 12.6 создается временный функтор, который может вызываться конструктором потока как функция без аргументов (требуется именно такая функция). Но прежде всего используйте typedef, чтобы указатель функции лучше воспринимался в тексте.

Typedef void (*WorkerFunPtr)(const std::string&);

Это создает тип WorkerFunPtr, который является указателем на функцию, принимающую по ссылке аргумент типа string и возвращающую тип void. После этого я создал шаблон класса Adapter. Он обеспечивает инстанцирование динамического функтора. Обратите внимание на конструктор:

Template<Typename FunT,

 typename ParamT>

Struct Adapter {

 Adapter(FunT f, ParamT& p) : f_(f), p_(&p) {}.

 // ...

Конструктор только инициализирует два члена, которые могут быть любого типа, но нам нужно, чтобы это был указатель на функцию и некоторый параметр p любого типа. Ради повышения эффективности я сохраняю адрес параметра, а не его значение.

Теперь рассмотрим следующую строку главного потока.

Boost::thread thr1(Adapter<WorkerFunPtr, std::string>(worker, s1)).

Аргумент конструктора thr1 представляет собой реализацию шаблона класса Adapter, использующую в качестве параметров два типа WorkerFunPtr и std::string. Это именно те два типа, которые являются членами адаптера f_ и p_. Наконец, Adapter перегружает operator(), поэтому он может вызываться как функция. Его вызов означает просто выполнение следующей функции.

F_(*p_);

Применяя шаблон класса Adapter, можно передавать аргументы функциям потока, причем делается это за счет лишь небольшого усложнения синтаксиса. Если требуется передавать еще один аргумент, просто добавьте дополнительный тип и переменную-член в шаблон Adapter. Этот подход привлекателен тем, что позволяет создавать набор шаблонов классов обобщенного адаптера и использовать их в различных контекстах.