Démarrer le fil avec la fonction membre


294

J'essaie de construire un std::threadavec une fonction membre qui ne prend aucun argument et retourne void. Je ne peux pas trouver de syntaxe qui fonctionne - le compilateur se plaint quoi qu'il arrive. Quelle est la bonne façon de l'implémenter spawn()pour qu'elle renvoie un std::threadqui s'exécute test()?

#include <thread>
class blub {
  void test() {
  }
public:
  std::thread spawn() {
    return { test };
  }
};

1
Voulez-vous dire que la fonction renvoie void, appelée void ou qu'elle n'a tout simplement aucun paramètre. Pouvez-vous ajouter le code de ce que vous essayez de faire?
Zaid Amir

Avez-vous testé? (Je ne l'ai pas encore fait.) Votre code semble s'appuyer sur le RVO (return-value-optimzation), mais je ne pense pas que vous soyez censé le faire. Je pense que l'utilisation std::move( std::thread(func) );est meilleure, car std::threadn'a pas de constructeur de copie.
RnMss

4
@RnMss: vous pouvez compter sur RVO , l'utilisation std::moveest redondante dans ce cas - si ce n'était pas vrai, et qu'il n'y avait pas de constructeur de copie, le compilateur donnerait quand même une erreur.
Qualia

Réponses:


367
#include <thread>
#include <iostream>

class bar {
public:
  void foo() {
    std::cout << "hello from member function" << std::endl;
  }
};

int main()
{
  std::thread t(&bar::foo, bar());
  t.join();
}

EDIT: Pour comptabiliser votre montage, vous devez le faire comme ceci:

  std::thread spawn() {
    return std::thread(&blub::test, this);
  }

MISE À JOUR: Je veux expliquer quelques points supplémentaires, certains d'entre eux ont également été discutés dans les commentaires.

La syntaxe décrite ci-dessus est définie en fonction de la définition INVOKE (§20.8.2.1):

Définissez INVOKE (f, t1, t2, ..., tN) comme suit:

  • (t1. * f) (t2, ..., tN) lorsque f est un pointeur vers 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 vers une fonction membre d'une classe T et t1 n'est pas l'un des types décrits dans l'article précédent;
  • t1. * f lorsque N == 1 et f est un pointeur vers les données de membre d'une classe T et t 1 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 lorsque N == 1 et f est un pointeur vers des données de membre d'une classe T et t 1 n'est pas l'un des types décrits dans l'article précédent;
  • f (t1, t2, ..., tN) dans tous les autres cas.

Un autre fait général que je veux souligner est que par défaut le constructeur de thread copiera tous les arguments qui lui sont passés. La raison en est que les arguments peuvent avoir besoin de survivre au thread appelant, la copie des arguments le garantit. Au lieu de cela, si vous voulez vraiment passer une référence, vous pouvez utiliser un std::reference_wrappercréé par std::ref.

std::thread (foo, std::ref(arg1));

En faisant cela, vous promettez de prendre soin de garantir que les arguments existeront toujours lorsque le thread les opèrera.


Notez que toutes les choses mentionnées ci-dessus peuvent également être appliquées à std::asyncet std::bind.


1
Au moins de cette façon, il compile. Bien que je ne sache pas pourquoi vous passez l'instance comme deuxième argument.
abergmeier

15
@LCID: la version multi-arguments du std::threadconstructeur fonctionne comme si les arguments étaient passés à std::bind. Pour appeler une fonction membre, le premier argument à std::binddoit être un pointeur, une référence ou un pointeur partagé vers un objet du type approprié.
Dave S

D'où tirez-vous que le constructeur agit comme un implicite bind? Je ne trouve ça nulle part.
Kerrek SB

3
@KerrekSB, comparez [thread.thread.constr] p4 avec [func.bind.bind] p3, la sémantique est assez similaire, définie en termes de pseudocode INVOKE, qui définit comment les fonctions membres sont appelées
Jonathan Wakely

4
rappelez-vous que les fonctions membres non statiques en tant que premier paramètre prennent une instance de classe (ce n'est pas visible pour le programmeur), donc lorsque vous passez cette méthode en tant que fonction brute, vous rencontrerez toujours un problème lors de la compilation et de la non-concordance de déclaration.
zoska

100

Puisque vous utilisez C ++ 11, l'expression lambda est une solution agréable et propre.

class blub {
    void test() {}
  public:
    std::thread spawn() {
      return std::thread( [this] { this->test(); } );
    }
};

puisqu'elle this->peut être omise, elle pourrait être raccourcie pour:

std::thread( [this] { test(); } )

ou juste

std::thread( [=] { test(); } )

8
En général, vous ne devez pas utiliser std::movelors du retour d'une variable locale par valeur. Cela inhibe en fait le RVO. Si vous retournez simplement par valeur (sans le déplacement), le compilateur peut utiliser RVO, et si ce n'est pas le cas, la norme dit qu'il doit invoquer la sémantique de déplacement.
zmb

@zmb, à l'exception de la compilation du code sur VC10, vous devez vous déplacer si le type de retour n'est pas CopyConstructable.
abergmeier

6
RVO génère toujours un meilleur code que la sémantique de déplacement et ne disparaît pas.
Jonathan Wakely

2
Soyez prudent avec [=]. Avec cela, vous pouvez copier par inadvertance un énorme objet. En général, c'est une odeur de code à utiliser [&]ou [=].
rustyx

3
@Tout le monde N'oubliez pas que c'est un fil ici. Cela signifie que la fonction lambda peut survivre à sa portée de contexte. Donc, en utilisant capturing-by-reference ( [&]), vous pouvez introduire des bogues comme certaines références pendantes. (Par exemple, std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); })
RnMss

29

Voici un exemple complet

#include <thread>
#include <iostream>

class Wrapper {
   public:
      void member1() {
          std::cout << "i am member1" << std::endl;
      }
      void member2(const char *arg1, unsigned arg2) {
          std::cout << "i am member2 and my first arg is (" << arg1 << ") and second arg is (" << arg2 << ")" << std::endl;
      }
      std::thread member1Thread() {
          return std::thread([=] { member1(); });
      }
      std::thread member2Thread(const char *arg1, unsigned arg2) {
          return std::thread([=] { member2(arg1, arg2); });
      }
};
int main(int argc, char **argv) {
   Wrapper *w = new Wrapper();
   std::thread tw1 = w->member1Thread();
   std::thread tw2 = w->member2Thread("hello", 100);
   tw1.join();
   tw2.join();
   return 0;
}

La compilation avec g ++ produit le résultat suivant

g++ -Wall -std=c++11 hello.cc -o hello -pthread

i am member1
i am member2 and my first arg is (hello) and second arg is (100)

10
pas vraiment pertinent pour la question OP, mais pourquoi allouez-vous Wrapper sur le tas (et pas le désallouer)? avez-vous une expérience java / c #?
Alessandro Teruzzi

N'oubliez pas deletela mémoire du tas :)
Slack Bot

19

@ hop5 et @RnMss ont suggéré d'utiliser des lambdas C ++ 11, mais si vous traitez avec des pointeurs, vous pouvez les utiliser directement:

#include <thread>
#include <iostream>

class CFoo {
  public:
    int m_i = 0;
    void bar() {
      ++m_i;
    }
};

int main() {
  CFoo foo;
  std::thread t1(&CFoo::bar, &foo);
  t1.join();
  std::thread t2(&CFoo::bar, &foo);
  t2.join();
  std::cout << foo.m_i << std::endl;
  return 0;
}

les sorties

2

Un échantillon réécrit de cette réponse serait alors:

#include <thread>
#include <iostream>

class Wrapper {
  public:
      void member1() {
          std::cout << "i am member1" << std::endl;
      }
      void member2(const char *arg1, unsigned arg2) {
          std::cout << "i am member2 and my first arg is (" << arg1 << ") and second arg is (" << arg2 << ")" << std::endl;
      }
      std::thread member1Thread() {
          return std::thread(&Wrapper::member1, this);
      }
      std::thread member2Thread(const char *arg1, unsigned arg2) {
          return std::thread(&Wrapper::member2, this, arg1, arg2);
      }
};

int main() {
  Wrapper *w = new Wrapper();
  std::thread tw1 = w->member1Thread();
  tw1.join();
  std::thread tw2 = w->member2Thread("hello", 100);
  tw2.join();
  return 0;
}

0

Certains utilisateurs ont déjà donné leur réponse et l'ont très bien expliqué.

Je voudrais ajouter quelques éléments supplémentaires liés au fil.

  1. Comment travailler avec functor et thread. Veuillez vous référer à l'exemple ci-dessous.

  2. Le thread fera sa propre copie de l'objet en passant l'objet.

    #include<thread>
    #include<Windows.h>
    #include<iostream>
    
    using namespace std;
    
    class CB
    {
    
    public:
        CB()
        {
            cout << "this=" << this << endl;
        }
        void operator()();
    };
    
    void CB::operator()()
    {
        cout << "this=" << this << endl;
        for (int i = 0; i < 5; i++)
        {
            cout << "CB()=" << i << endl;
            Sleep(1000);
        }
    }
    
    void main()
    {
        CB obj;     // please note the address of obj.
    
        thread t(obj); // here obj will be passed by value 
                       //i.e. thread will make it own local copy of it.
                        // we can confirm it by matching the address of
                        //object printed in the constructor
                        // and address of the obj printed in the function
    
        t.join();
    }

Une autre façon de réaliser la même chose est comme:

void main()
{
    thread t((CB()));

    t.join();
}

Mais si vous souhaitez passer l'objet par référence, utilisez la syntaxe ci-dessous:

void main()
{
    CB obj;
    //thread t(obj);
    thread t(std::ref(obj));
    t.join();
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.