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


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.