Как обернуть вызов конструктора `std::thread`? (работает с gcc, VS и icpc)

Исходное сообщение (с ошибками)

Я хочу обернуть вызов конструктора std::thread (чтобы отслеживать все запущенные потоки, чтобы я мог присоединиться к ним или сделать что-то еще). В этом примере поток t1 создается правильно, но поток t2 не использует gcc 4.8.1. Однако в Windows (VS2012) он компилируется без ошибок и работает без ошибок. Основываясь на обсуждении здесь, это может кажется ошибкой в ​​​​gcc, но, возможно, это ошибка в VS. Каков правильный способ сделать это?

#include <iostream>
#include <thread>

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

template<class F, class Arg>
std::thread& wrapper(F&& f, Arg&& a)
{
   std::thread* t = new std::thread(f,a,100);
   return *t;
}

int main()
{
    A a;

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

    std::thread t2 = wrapper(&A::foo, &a);
    t2.join();

    return 0;
}

Вот ошибка компилятора

-bash-4.1$ make
g++ -std=c++11    main.cpp   -o main
main.cpp: In function ‘int main()’:
main.cpp:23:41: error: use of deleted function ‘std::thread::thread(std::thread&)’
     std::thread t2 = wrapper(&A::foo, &a);
                                         ^
In file included from main.cpp:2:0:
/opt/rh/devtoolset-2/root/usr/include/c++/4.8.1/thread:125:5: error: declared here
     thread(thread&) = delete;
     ^
make: *** [all] Error 1

Обновлять

Я задал здесь неправильный вопрос и собираюсь удалить его, но, поскольку ответы полезны, я оставлю его. Проблема заключалась в том, что компилятор Intel icpc 14.0 (не gcc 4.8.1) был выдает ту же ошибку относительно bind, что обсуждалась здесь. И это не имеет ничего общего с «оболочкой», а просто вызывает std::thread с функцией-членом вместо статической функции.

Жалобы на утечку памяти верны на 100%, но это я неудачно упростил пример (из моего реального кода). Фактический код сохраняет потоки в контейнере и удаляет потоки при уничтожении.

Вот лучший пример:

#include <iostream>
#include <thread>

class A {
public:
    void foo() { }

    template<class Function, class Arg>
    std::thread* wrapper(Function&& f, Arg&& a)
    {
        auto t = new std::thread(f,a);
        return t;
    }
};

int main()
{
    A a;

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

    std::thread* t2 = a.wrapper(&A::foo, &a);
    t2->join();
    delete t2;

    return 0;
}

И вывод для g++ (4.8.1) который работает

-bash-4.1$ make CXX=g++
g++ -lpthread -std=c++11    main.cpp   -o main

и вывод для компилятора Intel icpc (14.0), который не работает

-bash-4.1$ make CXX=icpc
icpc -lpthread -std=c++11    main.cpp   -o main
/opt/rh/devtoolset-2/root/usr/include/c++/4.8.1/functional(1697): error: class "std::result_of<std::_Mem_fn<void (A::*)()> (A *)>" has no member "type"
        typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                         ^
          detected during:
            instantiation of class "std::_Bind_simple<_Callable (_Args...)> [with _Callable=std::_Mem_fn<void (A::*)()>, _Args=<A *>]" at line 1753
            instantiation of "std::_Bind_simple_helper<_Callable, _Args...>::__type std::__bind_simple(_Callable &&, _Args &&...) [with _Callable=void (A::*)(), _Args=<A *>]" at line 137 of "/opt/rh/devtoolset-2/root/usr/include/c++/4.8.1/thread"
            instantiation of "std::thread::thread(_Callable &&, _Args &&...) [with _Callable=void (A::*)(), _Args=<A *>]" at line 20 of "main.cpp"

/opt/rh/devtoolset-2/root/usr/include/c++/4.8.1/functional(1726): error: class "std::result_of<std::_Mem_fn<void (A::*)()> (A *)>" has no member "type"
          typename result_of<_Callable(_Args...)>::type
                                                   ^
          detected during:
            instantiation of class "std::_Bind_simple<_Callable (_Args...)> [with _Callable=std::_Mem_fn<void (A::*)()>, _Args=<A *>]" at line 1753
            instantiation of "std::_Bind_simple_helper<_Callable, _Args...>::__type std::__bind_simple(_Callable &&, _Args &&...) [with _Callable=void (A::*)(), _Args=<A *>]" at line 137 of "/opt/rh/devtoolset-2/root/usr/include/c++/4.8.1/thread"
            instantiation of "std::thread::thread(_Callable &&, _Args &&...) [with _Callable=void (A::*)(), _Args=<A *>]" at line 20 of "main.cpp"

compilation aborted for main.cpp (code 2)
make: *** [all] Error 2
-bash-4.1$

person Mark Lakata    schedule 08.05.2014    source источник
comment
Это ужасная обертка. Почему вы выделяете объекты динамически?!   -  person Kerrek SB    schedule 08.05.2014
comment
Ваша текущая оболочка также пропускает память. Вы вызываете new для создания объекта потока, но не вызываете delete для освобождения памяти.   -  person Alex Zywicki    schedule 08.05.2014
comment
Нет, это определенно ошибка в Visual Studio, и она на 100% не связана с предоставленной вами ссылкой, которая имеет отношение к std::ref, тогда как ваш вопрос касается конструкторов копирования потоков.   -  person Mooing Duck    schedule 08.05.2014
comment
@KerrekSB - согласен, это ужасная обертка. Это был плохой пример, но проблема не в утечке памяти, а в том, что я не могу заставить std::thread построить icpc, как показано.   -  person Mark Lakata    schedule 08.05.2014


Ответы (2)


Ваша обертка должна быть такой:

template<class F, class Arg>
std::thread wrapper(F&& f, Arg&& a)
{
    return std::thread(std::forward<F>(f), std::forward<Arg>(a));
}
person Kerrek SB    schedule 08.05.2014
comment
Это не имеет значения для компилятора Intel (версия icpc 14.0). - person Mark Lakata; 08.05.2014

std::thread нельзя скопировать. Учитывая, что wrapper возвращает std::thread&, wrapper(&A::foo, &a); является lvalue, поэтому для инициализации t2 требуется копия. Вот о чем ошибка компилятора.

К сожалению, некоторые версии Visual Studio с радостью выполнят этот переход, даже если здесь не используются значения rvalue.

Что-то вроде std::thread* t = new std::thread(f,a,100); return *t; очень похоже на return *new std::thread(f,a,100);, и это ясно показывает оператор утечки памяти: *new. Не делай этого.

Хотя std::thread нельзя копировать, его можно перемещать. Вам просто нужно сделать так, чтобы wrapper(&A::foo, &a); было rvalue. Это можно сделать, просто написав функцию в наиболее естественном стиле, например:

template<class F, class Arg>
std::thread wrapper(F&& f, Arg&& a)
{
    return std::thread(f, a, 100);
}

Однако это не позволяет правильно пересылать аргументы, сохраняя их категорию значений (как есть, он превращает rvalue в lvalue), даже если это не проблема для этого конкретного примера. Чтобы иметь общее применение, тело функции должно правильно пересылать аргументы, например: return std::thread(std::forward<F>(f), std::forward<Arg>(a), 100);.

person R. Martinho Fernandes    schedule 08.05.2014