С++ 11 Класс темы, как использовать функцию члена класса

Мои программы выглядят ниже

#include <iostream>
#include <thread>

class A {
public:
    void foo(int n ) { std::cout << n << std::endl; }
};

int main()
{
    A a;

    std::thread t1(&A::foo, std::ref(a), 100);

    t1.join();
    return 0;
}

Когда я скомпилирую его, используя следующую команду, я получаю ошибки

g++ -o main main.cc -lpthread -std=c++11

Ошибка:

In file included from /usr/local/include/c++/4.8.2/thread:39:0,
                  from check.cc:2:
/usr/local/include/c++/4.8.2/functional: In instantiation of ‘struct std::_Bind_simple<std::_Mem_fn<void (A::*)(int)>(std::reference_wrapper<A>, int)>’:
/usr/local/include/c++/4.8.2/thread:137:47:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (A::*)(int); _Args = {std::reference_wrapper<A>, int}]’
check.cc:13:42:   required from here
/usr/local/include/c++/4.8.2/functional:1697:61: error:no type named ‘type’ in ‘class std::result_of<std::_Mem_fn<void (A::*)(int)>(std::reference_wrapper<A>, int)>’
        typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                              ^
/usr/local/include/c++/4.8.2/functional:1727:9: error:no type named ‘type’ in ‘class std::result_of<std::_Mem_fn<void (A::*)(int)>(std::reference_wrapper<A>, int)>’
          _M_invoke(_Index_tuple<_Indices...>)
          ^

Ответ 1

Это неправильное место для ссылочной оболочки. Однако достаточно простого указателя и достигается желаемый результат:

std::thread t1(&A::foo, &a, 100);

Ответ 2

EDIT: RETRACTION

Здесь корректен Kerrek: я ошибочно предположил, что конструктор std::thread и std::bind были по дизайну идентичными интерфейсами. Однако автоматическое преобразование аргументов из reference_wrapper<A> в A& указано только для std::bind в [func.bind.bind]/10:

Значения связанных аргументов v1, v2, ..., vN и их соответствующих типов v1, v2, ..., vN зависят от типов TiD, полученных от вызова bind, и cv-квалификаторов cv оболочки обхода g следующим образом:

  • если TiD - reference_wrapper<T>, аргумент tid.get() и его тип Vi равен T&;
  • ...

Таким образом, это конкретное использование reference_wrapper<A> не поддерживается std::thread, но поддерживается std::bind. Тот факт, что std::thread ведет себя одинаково с std::bind в этом случае в других/более старых компиляторах, является ошибкой, а не версией выпусков GCC 4.8.

Я оставлю здесь неправильный ответ с этим объяснением в надежде, что другие не сделают эту ошибку в будущем.

Ответ на короткий (но НЕВЕРНЫЙ)

Это, по-видимому, ошибка в стандартной библиотеке, включенной в GCC 4.8. Код правильно скомпилирован:

Длинный (а также НЕВЕРНЫЙ) ответ:

Эффекты конструктора std::thread

template <class F, class ...Args>
explicit thread(F&& f, Args&&... args);

подробно описаны в С++ 11 30.3.1.2 [thread.thread.constr]/4:

Выполняется новый поток выполнения

INVOKE(DECAY_COPY(std::forward<F>(f)),
       DECAY_COPY(std::forward<Args>(args))...)

когда вызовы DECAY_COPY оцениваются в потоке конструирования.

DECAY_COPY описан в 30.2.6 [thread.decaycopy]/1:

В нескольких местах в этом разделе используется операция DECAY_COPY(x). Все такие виды использования означают функцию DECAY_COPY(x) и используют результат, где DECAY_COPY определяется следующим образом:

template <class T> typename decay<T>::type decay_copy(T&& v)
{ return std::forward<T>(v); }

В вызове в OP std::thread t1(&A::foo, std::ref(a), 100); все три аргумента являются значениями, которые DECAY_COPY будет реплицироваться в объекты в новой среде потока перед вызовом, эффект которого описан в 20.8.2 [func.require]/1:

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

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

Для кода в OP, f является указателем на функцию-член класса A со значением &A::foo, t1 является lvalue reference_wrapper<A>, чья сохраненная ссылка относится к A и t2 является int со значением 100. Используется вторая пуля 20.8.2/1. Поскольку t1 является reference_wrapper, *t1 оценивает хранимую ссылку (по 20.8.3.3/1), а вызов в новом потоке эффективно

(a.*&A::foo)(100);

Итак, да, стандарт описывает поведение OP точно так, как ожидалось.

EDIT: Как ни странно, GCC 4.8 правильно компилирует очень похожий пример:

class A {
public:
    void foo(int n) { std::cout << n << std::endl; }
};

int main()
{
    A a;
    auto foo = std::bind(&A::foo, std::ref(a), 100);
    foo();
}

Ответ 3

Что касается названия вопроса, я бы использовал лямбду для построения нитей. С ссылками или без ссылок через функции-члены-члены или параметры привязки.

 std::thread t1([&] { a.foo(100); });

Ответ 4

GCC 4.8 верна, std::thread и другие компоненты, определенные в терминах INVOKE, не должны быть реализованы в терминах std::bind. Они не должны вызывать вложенные выражения привязки и должны использовать совершенную пересылку для связанных аргументов (а не пересылать их как lvalues ​​как std::bind), и дополнительно, поскольку вы обнаружили, что они не разворачивают объекты reference_wrapper. В GCC 4.8 я представил внутреннюю деталь реализации __bind_simple для использования std::thread и т.д., Которая не имеет полного поведения std::bind.

В то время как другие отличия от std::bind желательны, я думаю, что операция INVOKE должна поддерживать объекты reference_wrapper, поэтому я подал отчет о дефекте, см. LWG 2219.

Ответ 5

Хорошо проблема ref (obj) возвращает ссылку (псевдоним) объекту, а не указателю (адресу)! для работы с потоками нам нужны указатели, а не ссылки! См. Ниже под рукой программу для использования указателей функций с потоками:

    #include <iostream>
    #include "vector"
    #include "string"
    #include "thread"
    #include "atomic"
    #include "functional"

    #include "stdlib.h"
    #include "stdio.h"
    #include "string.h"
    #include "assert.h"

    using namespace std;
    //__________________________Global variables_________________________________________________

    atomic<int> var(0);

    //__________________________class____________________________________________________________

    class C
    {
    public:

        C()
        {}

        static void addition (int a, int b)
        {
            for(int i= 0; i< a+b; i++)
                var++;
        }

        void subtraction (int a, int b)
        {
            for(int i= 0; i< a+b; i++)
                var--;
        }
    };

    class D : std::atomic<int>
    {
    public:
        D() : std::atomic<int>(0)
        {}

        void increase_member (int n)
        {
            for (int i=0; i<n; ++i)
                fetch_add(1);
        }

        int get_atomic_val()
        {
            return this->load();
        }
    };

    //________________________________functions________________________________________________

    void non_member_add (int a, int b)
    {
        for(int i= 0; i< a+b; i++)
            var++;
    }

    //__________________________________main____________________________________________________

    int main ()
    {
        int a=1, b=5;

    // (I)...........................................static public member function (with no inheritance).........................................

        void (* add_member_func_ptr)(int,int) = C::addition;            // pointer to a static public member function

        //defining thread pool for ststic public member_add_ptr

        vector<thread> thread_pool;

        for (int i=0; i<5; i++)
        {
            thread_pool.push_back(thread(add_member_func_ptr,a,b));
        }

        for(thread& thr: thread_pool)
            thr.join();

        cout<<"static public member function (with no inheritance)\t"<<var<<endl;

        //defining thread pool for ststic public member function

        var=0;

        thread_pool.clear();

        for (int i=0; i<5; i++)
        {
            thread_pool.push_back(thread(C::addition,a,b));             //void (* add_member_func_ptr)(int,int) is equal to C::addition
        }

        for(thread& thr: thread_pool)
            thr.join();

        cout<<"static public member function (with no inheritance)\t"<<var<<endl;

    // (II)..............................................non-static public member function (with no inheritance)...................................

        C bar;

        void (C::* sub_member_func_ptr)(int,int) = & C::subtraction;            // pointer to a non-static public member function

        var=0;

        //defining thread pool for non-ststic public member function

        thread_pool.clear();

        for (int i=0; i<5; i++)
        {
            thread_pool.push_back(thread(sub_member_func_ptr,bar,a,b));
        }

        for(thread& thr: thread_pool)
            thr.join();

        cout<<"non-static public member function (with no inheritance)\t"<<var<<endl;

        var=0;

        //defining thread pool for non-ststic public member function

        thread_pool.clear();

        for (int i=0; i<5; i++)
        {
            thread_pool.push_back(thread(&C::subtraction,bar,a,b));         //void (C::* sub_member_func_ptr)(int,int) equals & C::subtraction;
        }

        for(thread& thr: thread_pool)
            thr.join();

        cout<<"non-static public member function (with no inheritance)\t"<<var<<endl;


    // (III)................................................non-member function .................................................

        void (* non_member_add_ptr)(int,int) = non_member_add;              //pointer to a non-member function

        var=0;

        //defining thread pool for non_member_add

        thread_pool.clear();

        for (int i=0; i<5; i++)
        {
            thread_pool.push_back(thread(non_member_add,a,b));
        }

        for(thread& thr: thread_pool)
            thr.join();

        cout<<"non-member function\t"<<var<<endl<<endl;

    // (IV)...........................................non-static public member function (with inheritance).........................

        D foo;

        void (D::* member_func_ptr) (int) = & D::increase_member;                  //pointer to a non-static public member function of a derived class

        //defining thread pool for non-ststic public member function of a derived class

        thread_pool.clear();

        for (int i=0; i<5; i++)
        {
            thread_pool.push_back(thread(member_func_ptr,&foo,10));                 //use &foo because this is derived class!
        }

        for(thread& thr: thread_pool)
            thr.join();

        cout<<"non-static public member function (with inheritance)\t"<<foo.get_atomic_val()<<endl;

        //defining thread pool for non-ststic public member function

        D poo;

        thread_pool.clear();

        for (int i=0; i<5; i++)
        {
            reference_wrapper<D> poo_ref= ref(poo);

            D& poo_ref_= poo_ref.get();             //ref(obj) returns a reference (alias) to an object not a pointer(address)!

            D* d_ptr= &poo;                         //to work with thread we need pointers not references!


            thread_pool.push_back(thread(&D::increase_member, d_ptr,10));             //void (D::* member_func_ptr) (int) equals & D::increase_member;
        }

        for(thread& thr: thread_pool)
            thr.join();

        cout<<"non-static public member function (with inheritance)\t"<<poo.get_atomic_val()<<endl<<endl;


        return 0;
    }

Ответ 6

Просто хотел добавить, что я получил ту же ошибку, просто предоставив несовместимые аргументы для std:: bind/std:: thread. Как указание указателя на базовый класс, когда более конкретный указатель находился в сигнатуре фактической функции.