Начать тему с функции-члена


294

Я пытаюсь создать std::threadс помощью функции-члена, которая не принимает аргументов и возвращает void. Я не могу понять, какой синтаксис работает - компилятор жалуется, несмотря ни на что. Как правильно реализовать, spawn()чтобы он возвращал std::threadвыполняемое test()?

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

1
Вы имеете в виду, что функция возвращает void, называется void или просто не имеет никаких параметров. Можете ли вы добавить код для того, что вы пытаетесь сделать?
Заид Амир

Вы проверяли? (Пока нет.) Ваш код, кажется, полагается на RVO (оптимизацию возвращаемого значения), но я не думаю, что вы должны это делать. Я думаю, что использование std::move( std::thread(func) );лучше, так std::threadкак не имеет конструктора копирования.
RnMss 10.10.13

4
@RnMss: вы можете положиться на RVO , используя std::moveизбыточность в этом случае - если бы это было не так, и не было конструктора копирования, компилятор все равно выдал бы ошибку.
Qualia

Ответы:


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

РЕДАКТИРОВАТЬ: Учитывая ваши изменения, вы должны сделать это так:

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

ОБНОВЛЕНИЕ: Я хочу объяснить еще некоторые моменты, некоторые из них также обсуждались в комментариях.

Описанный выше синтаксис определен в терминах определения INVOKE (§20.8.2.1):

Определите INVOKE (f, t1, t2, ..., tN) следующим образом:

  • (t1. * f) (t2, ..., tN), когда f является указателем на функцию-член класса T, а t1 является объектом типа T или ссылкой на объект типа T или ссылкой на объект типа, производного от T;
  • ((* t1). * f) (t2, ..., tN), когда f является указателем на функцию-член класса T, а t1 не является одним из типов, описанных в предыдущем пункте;
  • t1. * f, когда N == 1 и f - указатель на данные члена класса T, а t 1 - объект типа T или
    ссылка на объект типа T или ссылка на объект
    типа, производного от Т;
  • (* t1). * f, когда N == 1 и f - указатель на данные члена класса T, а t 1 не является одним из типов, описанных в предыдущем пункте;
  • f (t1, t2, ..., tN) во всех остальных случаях.

Еще один общий факт, на который я хочу обратить внимание, заключается в том, что по умолчанию конструктор потока будет копировать все передаваемые ему аргументы. Причина этого в том, что аргументам может понадобиться пережить вызывающий поток, копирование аргументов гарантирует это. Вместо этого, если вы хотите действительно передать ссылку, вы можете использовать std::reference_wrapperсозданный std::ref.

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

Делая это, вы обещаете, что позаботитесь о том, чтобы аргументы все еще существовали, когда поток обрабатывает их.


Обратите внимание, что все вещи, упомянутые выше, также могут быть применены к std::asyncи std::bind.


1
По крайней мере, так он компилируется. Хотя я понятия не имею, почему вы передаете экземпляр в качестве второго аргумента.
abergmeier

15
@LCID: версия std::threadконструктора с несколькими аргументами работает так, как если бы аргументы были переданы std::bind. Для вызова функции-члена первым аргументом std::bindдолжен быть указатель, ссылка или общий указатель на объект соответствующего типа.
Дейв С

Откуда вы берете это, что конструктор действует как неявный bind? Я не могу найти это нигде.
Керрек С.Б.

3
@KerrekSB, сравните [thread.thread.constr] p4 с [func.bind.bind] p3, семантика очень похожа, она определена в терминах псевдокода INVOKE, который определяет способ вызова функций-членов
Джонатан Уэйкли,

4
помните, что не статические функции-члены в качестве первого параметра берут экземпляр класса (он не виден программисту), поэтому при передаче этого метода в виде необработанной функции вы всегда столкнетесь с проблемой при компиляции и несовпадении объявлений.
zoska

100

Поскольку вы используете C ++ 11, лямбда-выражение является хорошим и чистым решением.

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

так как this->может быть опущено, оно может быть сокращено до:

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

или просто

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

8
В общем, вы не должны использовать std::moveпри возврате локальной переменной по значению. Это на самом деле тормозит RVO. Если вы просто возвращаете по значению (без перемещения), компилятор может использовать RVO, а если нет, стандарт говорит, что он должен вызывать семантику перемещения.
2013 г.

@zmb, за исключением того, что вы хотите, чтобы код компилировался на VC10, вы должны двигаться, если тип возвращаемого значения не CopyConstructable.
Абергмайер

6
RVO все еще генерирует лучший код, чем семантика перемещения, и не исчезает.
Джонатан Уэйкли

2
Будьте осторожны с [=]. При этом вы можете случайно скопировать огромный объект. В общем, это кодовый запах для использования [&]или [=].
rustyx

3
@ Все, не забывайте, что это нить здесь. Это означает, что лямбда-функция может пережить свою контекстную область. Таким образом, используя метод capturing-by-reference ( [&]), вы можете вводить ошибки, например, висячие ссылки. (Например, std::thread spawn() { int i = 10; return std::thread( [&] { std::cout<<i<<"\n"; } ); })
RnMss

29

Вот полный пример

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

Компиляция с g ++ дает следующий результат

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
на самом деле не имеет отношения к вопросу OP, но почему вы размещаете Wrapper в куче (а не освобождаете его)? у вас есть java / c # фон?
Алессандро Теруцци

Не забудьте deleteна память из кучи :)
Slack Bot

19

@ hop5 и @RnMss предложили использовать лямбды C ++ 11, но если вы имеете дело с указателями, вы можете использовать их напрямую:

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

выходы

2

Переписанный образец из этого ответа будет тогда:

#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

Некоторые пользователи уже дали свой ответ и объяснили это очень хорошо.

Я хотел бы добавить еще несколько вещей, связанных с темой.

  1. Как работать с функтором и потоком. Пожалуйста, обратитесь к примеру ниже.

  2. Поток создаст свою собственную копию объекта при передаче объекта.

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

Еще один способ достичь того же:

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

    t.join();
}

Но если вы хотите передать объект по ссылке, используйте следующий синтаксис:

void main()
{
    CB obj;
    //thread t(obj);
    thread t(std::ref(obj));
    t.join();
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.