子类化std :: thread:constuctor中的可变参数模板函数的问题

时间:2017-05-16 21:08:05

标签: c++ multithreading c++11

我试图将std::thread子类化,以便在调用者的传入函数之前在新线程上执行子类的成员函数。类似于以下无效代码:

#include <thread>
#include <utility>

class MyThread : public std::thread {
    template<class Func, class... Args>
    void start(Func&& func, Args&&... args) {
        ... // Useful, thread-specific action
        func(args...);
    }
public:
    template<class Func, class... Args>
    MyThread(Func&& func, Args&&... args)
        : std::thread{[=]{start(std::forward<Func>(func),
                std::forward<Args>(args)...);}} {
    }
};

g++ -std=c++11上述代码存在以下问题:

MyThread.h: In lambda function:
MyThread.h:ii:jj: error: parameter packs not expanded with '...':
                   std::forward<Args>(args)...);}}
                                      ^

我在初始化列表中尝试了十几种不同的变体,但无济于事。

我怎样才能做我想做的事?

2 个答案:

答案 0 :(得分:1)

这应该这样做(提供c ++ 11和c ++ 14解决方案):

C ++ 14

#include <thread>
#include <utility>
#include <tuple>

class MyThread : public std::thread {

    template<class Func, class ArgTuple, std::size_t...Is>
    void start(Func&& func, ArgTuple&& args, std::index_sequence<Is...>) {
        // Useful, thread-specific action
        func(std::get<Is>(std::forward<ArgTuple>(args))...);
    }
public:
    template<class Func, class... Args>
    MyThread(Func&& func, Args&&... args)
        : std::thread
        {
            [this,
            func = std::forward<Func>(func), 
            args = std::make_tuple(std::forward<Args>(args)...)] () mutable
            {
                using tuple_type = std::decay_t<decltype(args)>;
                constexpr auto size = std::tuple_size<tuple_type>::value;
                this->start(func, std::move(args), std::make_index_sequence<size>());
            }
        } 
    {
    }
};

int main()
{
    auto x = MyThread([]{});
}

在C ++ 17中,它是微不足道的:

#include <thread>
#include <utility>
#include <tuple>
#include <iostream>

class MyThread : public std::thread {

public:
    template<class Func, class... Args>
    MyThread(Func&& func, Args&&... args)
        : std::thread
        {
            [this,
            func = std::forward<Func>(func), 
            args = std::make_tuple(std::forward<Args>(args)...)] () mutable
            {
                std::cout << "execute prolog here" << std::endl;

                std::apply(func, std::move(args));

                std::cout << "execute epilogue here" << std::endl;
            }
        } 
    {
    }
};

int main()
{
    auto x = MyThread([](int i){
        std::cout << i << std::endl;
    }, 6);
    x.join();
}

C ++ 11(我们必须将对象移动到可变lambda中,并提供缺少的std :: index_sequence):

#include <thread>
#include <utility>
#include <tuple>

namespace notstd
{
    using namespace std;

    template<class T, T... Ints> struct integer_sequence
    {};

    template<class S> struct next_integer_sequence;

    template<class T, T... Ints> struct next_integer_sequence<integer_sequence<T, Ints...>>
    {
        using type = integer_sequence<T, Ints..., sizeof...(Ints)>;
    };

    template<class T, T I, T N> struct make_int_seq_impl;

    template<class T, T N>
        using make_integer_sequence = typename make_int_seq_impl<T, 0, N>::type;

    template<class T, T I, T N> struct make_int_seq_impl
    {
        using type = typename next_integer_sequence<
            typename make_int_seq_impl<T, I+1, N>::type>::type;
    };

    template<class T, T N> struct make_int_seq_impl<T, N, N>
    {
        using type = integer_sequence<T>;
    };

    template<std::size_t... Ints>
        using index_sequence = integer_sequence<std::size_t, Ints...>;

    template<std::size_t N>
        using make_index_sequence = make_integer_sequence<std::size_t, N>;
}

template<class T>
struct mover
{
    mover(T const& value) : value_(value) {}
    mover(T&& value) : value_(std::move(value)) {}
    mover(const mover& other) : value_(std::move(other.value_)) {}

    T& get () & { return value_; }
    T&& get () && { return std::move(value_); }

    mutable T value_;
};

class MyThread : public std::thread {

    template<class Func, class ArgTuple, std::size_t...Is>
    void start(Func&& func, ArgTuple&& args, notstd::index_sequence<Is...>) {
        // Useful, thread-specific action
        func(std::get<Is>(std::forward<ArgTuple>(args))...);
    }
public:
    template<class Func, class... Args>
    MyThread(Func&& func, Args&&... args)
        : std::thread()
    {
        using func_type = typename std::decay<decltype(func)>::type;
        auto mfunc = mover<func_type>(std::forward<Func>(func));

        using arg_type = decltype(std::make_tuple(std::forward<Args>(args)...));
        auto margs = mover<arg_type>(std::make_tuple(std::forward<Args>(args)...));

        static_cast<std::thread&>(*this) = std::thread([this, mfunc, margs]() mutable
        {
                using tuple_type = typename std::remove_reference<decltype(margs.get())>::type;
                constexpr auto size = std::tuple_size<tuple_type>::value;
                this->start(mfunc.get(), std::move(margs).get(), notstd::make_index_sequence<size>());
        });

    }
};

int main()
{
    auto x = MyThread([](int i){}, 6);
    x.join();
}

答案 1 :(得分:1)

我之前遇到的最大困难是获得std::thread的所有行为。它的构造函数不仅可以使用指向自由函数的指针,还可以使用类方法指针,然后将类的对象作为第一个参数。它有很多变化:类方法,类函数对象数据成员,类类型的对象与指向对象的指针等。

这适用于C ++ 14:

class MyThread : public std::thread {
    void prolog() const { std::cout << "prolog\n"; }

 public:
    template <typename... ArgTypes>
    MyThread(ArgTypes&&... args) :
        std::thread(
            [this, bfunc = std::bind(std::forward<ArgTypes>(args)...)]
            () mutable {
                prolog();
                bfunc();
            })
    { }
};

如果你将prolog代码放在lambda中并且它没有调用类方法,那么就不需要捕获this

对于C ++ 11,由于缺少捕获初始值设定项,因此需要进行小的更改,因此必须将绑定作为参数传递给std::thread

std::thread(
    [this]
    (decltype(std::bind(std::forward<ArgTypes>(args)...))&& bfunc) mutable {
        prolog();
        bfunc();
    }, std::bind(std::forward<ArgTypes>(args)...))

这是一个测试程序,它也运用std::thread的类成员形式:

int main()
{
    auto x = MyThread([](){ std::cout << "lambda\n"; });
    x.join();

    struct mystruct {
        void func() { std::cout << "mystruct::func\n"; }
    } obj;
    auto y = MyThread(&mystruct::func, &obj);
    y.join();

    return 0;
}

我还没有检查过,但我有点担心在某些情况下,this的捕获(在其他解决方案中也会看到)并不安全。考虑对象何时是移动的右值,如std::thread t = MyThread(args)中所示。我认为MyThread对象在它创建的线程必须使用它之前就会消失。 &#34;线程&#34;将被移动到一个新对象并仍在运行,但捕获的此指针将指向一个现在陈旧的对象。

我认为您需要确保在使用所有引用或指向类或类成员的指针完成新线程之前,构造函数不会返回。在可能的情况下,按价值捕获会有所帮助。或许prolog()可能是一个静态类方法。