web-dev-qa-db-fra.com

c ++ 11 Classe de thread comment utiliser une fonction membre de la classe

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...>)
          ^
19
hysteria

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);
25
Kerrek SB

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 correspondants V1, V2, ..., VN dépendent des types TiD dérivés de l'appel à bind et des cv -qualificateurs cv de l'encapsuleur g comme suit:

  • si TiD est reference_wrapper<T>, l'argument est tid.get() et son type Vi est T&;
  • ...

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 fonction decay_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) quand f est un pointeur sur une fonction membre d'une classe T et t1 est un objet de type T ou une référence à un objet de type T ou une référence à un objet d'un type dérivé de T;
  • ((*t1).*f)(t2, ..., tN) lorsque f est un pointeur sur une fonction membre d'une classe T et t1 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();
}
11
Casey

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); });
10
zahir

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 .

7
Jonathan Wakely

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.

0
Hugo Maxwell

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;
    }
0
MCH