Mes programmes ressemblent à ceux ci-dessous
#include <iostream>
#include <thread>
class A {
public:
void foo(int n ) { std::cout << n << std::endl; }
};
int main()
{
A a;
std::thread t1(&A::foo, std::ref(a), 100);
t1.join();
return 0;
}
Quand je le compile en utilisant la commande suivante, j'obtiens des erreurs
g++ -o main main.cc -lpthread -std=c++11
Erreur:
In file included from /usr/local/include/c++/4.8.2/thread:39:0,
from check.cc:2:
/usr/local/include/c++/4.8.2/functional: In instantiation of ‘struct std::_Bind_simple<std::_Mem_fn<void (A::*)(int)>(std::reference_wrapper<A>, int)>’:
/usr/local/include/c++/4.8.2/thread:137:47: required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (A::*)(int); _Args = {std::reference_wrapper<A>, int}]’
check.cc:13:42: required from here
/usr/local/include/c++/4.8.2/functional:1697:61: error:no type named ‘type’ in ‘class std::result_of<std::_Mem_fn<void (A::*)(int)>(std::reference_wrapper<A>, int)>’
typedef typename result_of<_Callable(_Args...)>::type result_type;
^
/usr/local/include/c++/4.8.2/functional:1727:9: error:no type named ‘type’ in ‘class std::result_of<std::_Mem_fn<void (A::*)(int)>(std::reference_wrapper<A>, int)>’
_M_invoke(_Index_Tuple<_Indices...>)
^
Ce n'est pas le bon endroit pour un wrapper de référence. Un simple pointeur suffit cependant et permet d'obtenir le résultat souhaité:
std::thread t1(&A::foo, &a, 100);
EDIT: RETRACTION
Kerrek a raison ici: j'ai supposé à tort que les constructeurs std::thread
et std::bind
étaient par nature des interfaces identiques. Cependant, la conversion automatique des arguments de reference_wrapper<A>
à A&
est spécifiée uniquement pour std::bind
dans [func.bind.bind]/10:
Les valeurs des arguments liés
v1, v2, ..., vN
et de leurs types correspondantsV1, V2, ..., VN
dépendent des typesTiD
dérivés de l'appel àbind
et des cv -qualificateurs cv de l'encapsuleurg
comme suit:
- si
TiD
estreference_wrapper<T>
, l'argument esttid.get()
et son typeVi
estT&
;- ...
Donc, cette utilisation particulière de reference_wrapper<A>
est pas prise en charge par std::thread
, mais est prise en charge par std::bind
. Le fait que std::thread
se comporte de manière identique à std::bind
dans ce cas dans d'autres compilateurs/anciens est le bogue, pas le comportement des versions GCC de 4,8 lignes.
Je laisserai la mauvaise réponse ici avec cette explication dans l'espoir que d'autres ne commettront pas la même erreur à l'avenir.
Réponse courte (mais INCORRECTE)
Ceci est apparemment un bogue de la bibliothèque standard incluse avec GCC 4.8. Le code est correctement compilé par:
Réponse longue (et aussi INCORRECT):
Les effets du constructeur std::thread
template <class F, class ...Args>
explicit thread(F&& f, Args&&... args);
sont détaillés dans C++ 11 30.3.1.2 [thread.thread.constr]/4:
Le nouveau thread d'exécution s'exécute
INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...)
avec les appels à
DECAY_COPY
en cours d'évaluation dans le thread de construction.
DECAY_COPY
est décrit dans 30.2.6 [thread.decaycopy]/1:
A plusieurs endroits de cette clause, l'opération
DECAY_COPY(x)
est utilisée. Toutes ces utilisations impliquent d'appeler la fonctiondecay_copy(x)
et d'utiliser le résultat, oùdecay_copy
est défini comme suit:template <class T> typename decay<T>::type decay_copy(T&& v) { return std::forward<T>(v); }
Dans l'invocation de l'OP std::thread t1(&A::foo, std::ref(a), 100);
, les trois arguments sont des valeurs que DECAY_COPY
va répliquer en objets du nouvel environnement de thread avant l'appel, dont l'effet est décrit dans 20.8.2 [func.require]/1:
Définissez
INVOKE(f, t1, t2, ..., tN)
comme suit:
(t1.*f)(t2, ..., tN)
quandf
est un pointeur sur une fonction membre d'une classeT
ett1
est un objet de typeT
ou une référence à un objet de typeT
ou une référence à un objet d'un type dérivé deT
;((*t1).*f)(t2, ..., tN)
lorsquef
est un pointeur sur une fonction membre d'une classeT
ett1
n'est pas l'un des types décrits dans l'élément précédent;- ...
Pour le code de l'OP, f
est un pointeur sur la fonction membre de la classe A
avec la valeur &A::foo
, t1
est une lvalue reference_wrapper<A>
dont la référence stockée fait référence à a
, et t2
est une int
avec la valeur 100
. La deuxième puce de 20.8.2/1 s’applique. Puisque t1
est un reference_wrapper
, *t1
correspond à la référence stockée (conformément à 20.8.3.3/1) et l'invocation dans le nouveau thread est effectivement effectuée.
(a.*&A::foo)(100);
Donc, oui, la norme décrit le comportement de l'OP exactement comme prévu.
EDIT: Bizarrement, GCC 4.8 compile correctement le très similaire exemple :
class A {
public:
void foo(int n) { std::cout << n << std::endl; }
};
int main()
{
A a;
auto foo = std::bind(&A::foo, std::ref(a), 100);
foo();
}
En ce qui concerne le titre de votre question, j’utiliserais un lambda pour la construction du fil. Avec ou sans références, via des fonctions membres appelantes ou des paramètres de liaison.
std::thread t1([&] { a.foo(100); });
GCC 4.8 est correct, std::thread
et les autres composants définis en termes de INVOKE ne doivent pas être implémentés en termes de std::bind
. Ils ne doivent pas invoquer des expressions de liaison imbriquées ni utiliser une transmission parfaite pour les arguments liés (plutôt que de les transmettre sous la forme de valeurs lvalues comme le fait std::bind
) et, en outre, ils ne décompressent pas les objets reference_wrapper
. Dans GCC 4.8, j’ai introduit un détail d’implémentation interne, __bind_simple
, à utiliser par std::thread
etc. qui n’a pas le comportement complet std::bind
.
Bien que les autres différences par rapport à std::bind
soient souhaitables, je pense que l’opération INVOKE devrait toujours prendre en charge les objets reference_wrapper
. J’ai donc déposé un rapport de défectuosité, voir LWG 2219 .
Je voulais juste ajouter que j'ai eu la même erreur en donnant simplement des arguments incompatibles à std :: bind/std :: thread. C'est comme donner un pointeur à une classe de base lorsqu'un pointeur plus spécifique se trouvait dans la signature de la fonction réelle.
Ok le problème est ref (obj) renvoie une référence (alias) à un objet et non à un pointeur (adresse)! pour travailler avec des threads, nous avons besoin de pointeurs et non de références! Voir ci-dessous un programme pratique pour utiliser les pointeurs de fonction avec des threads:
#include <iostream>
#include "vector"
#include "string"
#include "thread"
#include "atomic"
#include "functional"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "assert.h"
using namespace std;
//__________________________Global variables_________________________________________________
atomic<int> var(0);
//__________________________class____________________________________________________________
class C
{
public:
C()
{}
static void addition (int a, int b)
{
for(int i= 0; i< a+b; i++)
var++;
}
void subtraction (int a, int b)
{
for(int i= 0; i< a+b; i++)
var--;
}
};
class D : std::atomic<int>
{
public:
D() : std::atomic<int>(0)
{}
void increase_member (int n)
{
for (int i=0; i<n; ++i)
fetch_add(1);
}
int get_atomic_val()
{
return this->load();
}
};
//________________________________functions________________________________________________
void non_member_add (int a, int b)
{
for(int i= 0; i< a+b; i++)
var++;
}
//__________________________________main____________________________________________________
int main ()
{
int a=1, b=5;
// (I)...........................................static public member function (with no inheritance).........................................
void (* add_member_func_ptr)(int,int) = C::addition; // pointer to a static public member function
//defining thread pool for ststic public member_add_ptr
vector<thread> thread_pool;
for (int i=0; i<5; i++)
{
thread_pool.Push_back(thread(add_member_func_ptr,a,b));
}
for(thread& thr: thread_pool)
thr.join();
cout<<"static public member function (with no inheritance)\t"<<var<<endl;
//defining thread pool for ststic public member function
var=0;
thread_pool.clear();
for (int i=0; i<5; i++)
{
thread_pool.Push_back(thread(C::addition,a,b)); //void (* add_member_func_ptr)(int,int) is equal to C::addition
}
for(thread& thr: thread_pool)
thr.join();
cout<<"static public member function (with no inheritance)\t"<<var<<endl;
// (II)..............................................non-static public member function (with no inheritance)...................................
C bar;
void (C::* sub_member_func_ptr)(int,int) = & C::subtraction; // pointer to a non-static public member function
var=0;
//defining thread pool for non-ststic public member function
thread_pool.clear();
for (int i=0; i<5; i++)
{
thread_pool.Push_back(thread(sub_member_func_ptr,bar,a,b));
}
for(thread& thr: thread_pool)
thr.join();
cout<<"non-static public member function (with no inheritance)\t"<<var<<endl;
var=0;
//defining thread pool for non-ststic public member function
thread_pool.clear();
for (int i=0; i<5; i++)
{
thread_pool.Push_back(thread(&C::subtraction,bar,a,b)); //void (C::* sub_member_func_ptr)(int,int) equals & C::subtraction;
}
for(thread& thr: thread_pool)
thr.join();
cout<<"non-static public member function (with no inheritance)\t"<<var<<endl;
// (III)................................................non-member function .................................................
void (* non_member_add_ptr)(int,int) = non_member_add; //pointer to a non-member function
var=0;
//defining thread pool for non_member_add
thread_pool.clear();
for (int i=0; i<5; i++)
{
thread_pool.Push_back(thread(non_member_add,a,b));
}
for(thread& thr: thread_pool)
thr.join();
cout<<"non-member function\t"<<var<<endl<<endl;
// (IV)...........................................non-static public member function (with inheritance).........................
D foo;
void (D::* member_func_ptr) (int) = & D::increase_member; //pointer to a non-static public member function of a derived class
//defining thread pool for non-ststic public member function of a derived class
thread_pool.clear();
for (int i=0; i<5; i++)
{
thread_pool.Push_back(thread(member_func_ptr,&foo,10)); //use &foo because this is derived class!
}
for(thread& thr: thread_pool)
thr.join();
cout<<"non-static public member function (with inheritance)\t"<<foo.get_atomic_val()<<endl;
//defining thread pool for non-ststic public member function
D poo;
thread_pool.clear();
for (int i=0; i<5; i++)
{
reference_wrapper<D> poo_ref= ref(poo);
D& poo_ref_= poo_ref.get(); //ref(obj) returns a reference (alias) to an object not a pointer(address)!
D* d_ptr= &poo; //to work with thread we need pointers not references!
thread_pool.Push_back(thread(&D::increase_member, d_ptr,10)); //void (D::* member_func_ptr) (int) equals & D::increase_member;
}
for(thread& thr: thread_pool)
thr.join();
cout<<"non-static public member function (with inheritance)\t"<<poo.get_atomic_val()<<endl<<endl;
return 0;
}