Sunday, April 20, 2014

С++11 std::thread и передача переменных по ссылке

Издавна любители С были озабочены наличием единственного способа передачи переменных в функцию - по значению. Из этого следует, что внутри функции всегда копия переменной и как бы мы не меняли ее внутри функции, при выходе из нее значение переменной останется прежним.
Поэтому, если хочется менять переменную внутри функции, ее надо передавать по указателю. Работа с указателями для невнимательных людей типа меня может оказаться сущим адом, причем ошибку не всегда просто найти и понять.
Поэтому, разработчики языка С++ придумали механизм работы с "ссылками". Если в объявлении функции перед именем переменной стоит амперсанд "&", то С++ понимает, что данную переменную надо передавать в функцию по ссылке, - это означает, что изменения этой переменной, произведенные внутри функции, будут видны за пределами функции.
Отличная история! Теперь надо просто понимать какие переменные будут меняться в функции и ставить перед их именами амперсанд в объявлении функции. Далее работа с этими "ссылками" как с обычными переменными.

В спецификации С++11 появились потоки (класс std::thread). Ура! Не прошло и 30-и лет, как С++ предоставил API под многопоточное программирование. Синтаксис несложен: при создании потока указывается функция, которая, собственно, определяет, что поток делает.
Иногда хочется, чтобы поток мог менять переменные, поэтому хочется воспользоваться описанными выше достижениями С++ - "ссылками".
Можно попробовать оба варианта:
1. простая работа с ссылками, как-будто потоков и нет: в объявлении функции амперсанд перед именем переменной, далее - как обычно.
2. воспользоваться враппером std::ref(<имя переменной>) при объявлении функции вместо простого амперсанда, далее - как обычно - при вызове функции переменная передается обычным синтаксисом, как это было бы при стандартной передаче по значению.
Однако, оба варианта не работают в gcc 4.8, даже несмотря на то, что второй - в строгом соответствии со спецификацией С++11.
При компиляции с использованием gcc 4.8.2 высыпаются загадочные, но одинаковые в обоих случаях попытки использования ссылок, ошибки, привожу пример:
root@kali:~/src/JavaCG# make Brute.o
g++ -std=c++11 -pthread -c JavaCG/Brute.cpp -o Release/Brute.o
In file included from /usr/local/include/c++/4.8.2/thread:39:0,
                 from JavaCG/Brute.h:12,
                 from JavaCG/Brute.cpp:4:
/usr/local/include/c++/4.8.2/functional: In instantiation of ‘struct std::_Bind_simple’:
/usr/local/include/c++/4.8.2/thread:137:47:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(int, _found*, SafeLong*, int**, int, Options&); _Args = {int&, _found*&, SafeLong*, int**&, int&, Options&}]’
JavaCG/Brute.cpp:192:83:   required from here
/usr/local/include/c++/4.8.2/functional:1697:61: error: no type named ‘type’ in ‘class std::result_of
       typedef typename result_of<_callable rgs...="">::type result_type;
                                                             ^
/usr/local/include/c++/4.8.2/functional:1727:9: error: no type named ‘type’ in ‘class std::result_of
         _M_invoke(_Index_tuple<_indices ...="">)
         ^
/usr/local/include/c++/4.8.2/functional: In instantiation of ‘struct std::_Bind_simple’:
/usr/local/include/c++/4.8.2/thread:137:47:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(int, _found*, int**, int, Options&); _Args = {int&, _found*, int**&, int&, Options&}]’
JavaCG/Brute.cpp:215:70:   required from here
/usr/local/include/c++/4.8.2/functional:1697:61: error: no type named ‘type’ in ‘class std::result_of
       typedef typename result_of<_callable rgs...="">::type result_type;
                                                             ^
/usr/local/include/c++/4.8.2/functional:1727:9: error: no type named ‘type’ in ‘class std::result_of
         _M_invoke(_Index_tuple<_indices ...="">)
         ^
/usr/local/include/c++/4.8.2/functional: In instantiation of ‘struct std::_Bind_simple’:
/usr/local/include/c++/4.8.2/thread:137:47:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (&)(int, _found*, SafeLong*, _periods&, int**, int, Options&); _Args = {int&, _found*&, SafeLong*, _periods&, int**&, int&, Options&}]’
JavaCG/Brute.cpp:467:92:   required from here
/usr/local/include/c++/4.8.2/functional:1697:61: error: no type named ‘type’ in ‘class std::result_of
       typedef typename result_of<_callable rgs...="">::type result_type;
                                                             ^
.........пропущено....................


make: *** [Brute.o] Error 1


Для случая Visual Studio 2013 вариант с амперсандом (&) не приводит к ошибке компиляции, но при этом переменная передается по значению, как и в случае без амперсанда. Вариант с враппером std::ref(<имя переменной>) работает корректно, как и предсказано спецификацией С++2011.

Писать в Visual Studio лично мне несравненно удобнее, чем в редакторе vi, поэтому, конечно же, я пишу изначально на VC++2013. Однако при переносе в Linux/gcc4.8.2, сломал весь мозг, что означают указанные выше ошибки.

И ни один выкинутый Гуглем пост мне не посоветовал просто: "Перестань пытаться использовать ссылки в функции для потока std::theread! Используй старые добрые указатели!"

В общем, независимо от того, баг это или фича, - не используйте ссылки при передачи параметров в функции потоков std::theread, используйте указатели!

No comments: