Початкова нитка з функцією члена


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

4
@RnMss: ви можете покластися на RVO , використовуючи зайве std::moveв цьому випадку - якби це не було правдою, і не було конструктора копій, компілятор все-таки видав би помилку.
Квалія

Відповіді:


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 або посилання на об’єкт типу, похідного від Т;
  • ((* 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повинен бути вказівник, посилання або загальний вказівник на об’єкт відповідного типу.
Дейв S

Звідки ви це берете, що конструктор діє як неявний bind? Я ніде цього не можу знайти.
Керрек СБ

3
@KerrekSB, порівняйте [thread.thread.constr] p4 з [func.bind.bind] p3, семантика досить схожа, визначена в термінах псевдокоду INVOKE, який визначає, як називаються функції учасників
Jonathan Wakely

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, а якщо це не стандарт, то він повинен використовувати семантику переміщення.
zmb

@zmb, за винятком того, що ви хочете зібрати код на VC10, вам потрібно перемістити, якщо тип повернення не є CopyConstructable.
abergmeier

6
RVO все ще генерує кращий код, ніж семантика переміщення, і не йде.
Джонатан Вейклі

2
Будьте обережні [=]. З цим ви можете ненароком скопіювати величезний об’єкт. Взагалі, це кодовий запах для використання [&]або [=].
rustyx

3
@Everyone Не забудьте, що це нитка тут. Це означає, що лямбда-функція може переживати її контекстну область. Отже, використовуючи функцію 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
Насправді не стосується питання щодо ОП, але чому ви виділяєте 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.