Iniciar thread com função membro

294

Eu estou tentando construir um std::threadcom uma função de membro que não leva argumentos e retornos void. Não consigo descobrir nenhuma sintaxe que funcione - o compilador reclama, não importa o quê. Qual é a maneira correta de implementar spawn()para que ele retorne um std::threadque seja executado test()?

#include <thread>
class blub {
  void test() {
  }
public:
  std::thread spawn() {
    return { test };
  }
};
abergmeier
fonte
1
Você quer dizer que a função retorna nula, chamada nula ou simplesmente não possui parâmetros. Você pode adicionar o código para o que você está tentando fazer?
Zaid Amir
Você já testou? (Ainda não o fiz.) Seu código parece depender do RVO (otimização do valor de retorno), mas acho que você não deve fazê-lo. Eu acho que usar std::move( std::thread(func) );é melhor, pois std::threadnão tem um construtor de cópias.
RnMss
4
@RnMss: você pode confiar no RVO , usando std::moveneste caso é redundante - se isso não fosse verdade e não houvesse construtor de cópias, o compilador apresentaria um erro de qualquer maneira.
Qualia

Respostas:

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: contabilizando sua edição, você deve fazer assim:

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

ATUALIZAÇÃO: Quero explicar mais alguns pontos, alguns deles também foram discutidos nos comentários.

A sintaxe descrita acima é definida em termos da definição INVOKE (§20.8.2.1):

Defina INVOKE (f, t1, t2, ..., tN) da seguinte maneira:

  • (t1. * f) (t2, ..., tN) quando f é um ponteiro para uma função membro de uma classe T e t1 é um objeto do tipo T ou uma referência a um objeto do tipo T ou uma referência a um objeto de um tipo derivado de T;
  • ((* t1). * f) (t2, ..., tN) quando f é um ponteiro para uma função membro de uma classe T e t1 não é um dos tipos descritos no item anterior;
  • t1. * f quando N == 1 ef é um ponteiro para dados de membros de uma classe T e t 1 é um objeto do tipo T ou uma
    referência a um objeto do tipo T ou uma referência a um objeto de um
    tipo derivado de T;
  • (* t1). * f quando N == 1 ef é um ponteiro para dados de membros de uma classe T e t 1 não é um dos tipos descritos no item anterior;
  • f (t1, t2, ..., tN) em todos os outros casos.

Outro fato geral que quero destacar é que, por padrão, o construtor de threads copia todos os argumentos passados ​​para ele. A razão para isso é que os argumentos podem precisar sobreviver ao segmento de chamada, copiar os argumentos garante isso. Em vez disso, se você quiser realmente passar uma referência, poderá usar um std::reference_wrappercriado por std::ref.

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

Ao fazer isso, você promete que garantirá que os argumentos ainda existam quando o encadeamento operar neles.


Observe que todas as coisas mencionadas acima também podem ser aplicadas a std::asynce std::bind.

Stephan Dollberg
fonte
1
Pelo menos assim, ele compila. Embora eu não tenha idéia de por que você está passando a instância como o segundo argumento.
Abergmeier 20/05
15
@LCID: A versão com vários argumentos do std::threadconstrutor funciona como se os argumentos fossem passados ​​para std::bind. Para chamar uma função de membro, o primeiro argumento std::binddeve ser um ponteiro, referência ou ponteiro compartilhado para um objeto do tipo apropriado.
Dave S
De onde você tira que o construtor age como implícito bind? Não consigo encontrar isso em lugar nenhum.
Kerrek SB
3
@KerrekSB, compare [thread.thread.constr] p4 com [func.bind.bind] p3, a semântica é bastante semelhante, definida em termos do pseudocódigo INVOKE, que define como as funções de membro são chamadas
Jonathan Wakely
4
lembre-se de que funções membro não estáticas como primeiro parâmetro recebem instância da classe (não é visível para programador); portanto, ao passar esse método como função bruta, você sempre encontrará um problema durante a incompatibilidade de compilação e declaração.
zoska
100

Como você está usando o C ++ 11, a expressão lambda é uma solução agradável e limpa.

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

como this->pode ser omitido, pode ser reduzido para:

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

ou apenas

std::thread( [=] { test(); } )
RnMss
fonte
8
Em geral, você não deve usar std::moveao retornar uma variável local por valor. Isso realmente inibe o RVO. Se você apenas retornar por valor (sem a movimentação), o compilador poderá usar o RVO, e se isso não acontecer, o padrão diz que ele deve chamar a semântica da movimentação.
Zmb
@zmb, com a exceção de que você deseja que o código seja compilado no VC10, você deve mudar se o tipo de retorno não for CopyConstructable.
abergmeier
6
O RVO ainda gera código melhor que a semântica de movimentação e não está desaparecendo.
Jonathan Wakely
2
Tenha cuidado com [=]. Com isso, você pode copiar inadvertidamente um objeto enorme. Em geral, é um cheiro de código para usar [&]ou [=].
Rustyx 9/09/16
3
@ Todo mundo Não se esqueça que é um tópico aqui. Isso significa que a função lambda pode sobreviver ao seu escopo de contexto. Portanto, usando a captura por referência ( [&]), você pode introduzir bugs, como algumas referências pendentes. (Por exemplo, std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); })
RnMss
29

Aqui está um exemplo completo

#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;
}

Compilar com g ++ produz o seguinte resultado

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)
hop5
fonte
10
não é realmente relevante para a questão do OP, mas por que você aloca o Wrapper no heap (e não o desaloca)? você tem java / c # background?
Alessandro Teruzzi
Não se esqueça deleteda memória da pilha :)
Slack Bot
19

O @ hop5 e o @RnMss sugeriram o uso de lambdas C ++ 11, mas se você lida com ponteiros, pode usá-los diretamente:

#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;
}

saídas

2

A amostra reescrita desta resposta seria então:

#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;
}
Andrey Starodubtsev
fonte
0

Alguns usuários já deram sua resposta e a explicaram muito bem.

Gostaria de adicionar mais algumas coisas relacionadas ao segmento.

  1. Como trabalhar com functor e thread. Por favor, consulte o exemplo abaixo.

  2. O thread fará sua própria cópia do objeto ao passar o objeto.

    #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();
    }

Outra maneira de conseguir a mesma coisa é:

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

    t.join();
}

Mas se você deseja passar o objeto por referência, use a sintaxe abaixo:

void main()
{
    CB obj;
    //thread t(obj);
    thread t(std::ref(obj));
    t.join();
}
Mohit
fonte