【问题标题】:How do I forward a function and a parameter pack into a lambda, into the constructor of std::thread?如何将函数和参数包转发到 lambda 到 std::thread 的构造函数中?
【发布时间】:2015-01-03 05:13:28
【问题描述】:

我希望按照this answer 的思路在 C++11 中实现一个“可中断”线程。

我已经参数化了我的可中断类,以便它可以包装任何具有类似构造函数的线程类(用于 std::thread,但也应该适用于 boost::thread 之类的东西(没关系 boost::thread 已经有这个功能))。那部分应该无关紧要。

尽管链接的答案有一些我必须纠正的问题,但这对我来说很有意义,所以我想弄清楚我哪里出错了。

我已经包含了相关的来源和结果。

interruptible.hpp

#include <atomic>
#include <exception>
#include <thread>

class interrupted_exception : public virtual std::exception
{
public:
    char const *what() const noexcept { return "interrupted"; }
};


template <typename T>
class interruptible
{
public:
    template <typename F, typename... A>
    interruptible(F&& Function, A&&... Arguments) :
        Interrupted(false),
        Thread(
            [](std::atomic_bool *Interrupted, F&& Function, A&&... Arguments)
            {
                LocalInterrupted = Interrupted;
                Function(std::forward<A>(Arguments)...);
            },
            &this->Interrupted,
            std::forward<F>(Function),
            std::forward<A>(Arguments)...
        )
    { }

    void interrupt()            { this->Interrupted = true; }
    bool interrupted() const    { return this->Interrupted; }

    T *operator->()             { return &this->Thread; }

    static inline void check() noexcept(false)
    {
        if (!interruptible::LocalInterrupted)
            return;

        if (!interruptible::LocalInterrupted->load())
            return;

        throw interrupted_exception();
    }

private:
    static thread_local std::atomic_bool *LocalInterrupted;

    std::atomic_bool    Interrupted;
    T                   Thread;
};

template <typename T>
thread_local std::atomic_bool *interruptible<T>::LocalInterrupted = nullptr;

main.cpp

#include <iostream>
#include <unistd.h>
#include <thread>

#include "interruptible.hpp"

void DoStuff()
{
    try
    {
        while (true)
        {
            std::cout << "Loop" << std::endl;

            sleep(1);

            interruptible<std::thread>::check();
        }
    }
    catch (interrupted_exception const &e)
    {
        std::cout << "Interrupted!" << std::endl;
    }
}


int main()
{
    interruptible<std::thread> a(DoStuff);

    sleep(2);

    std::cout << "Interrupting..." << std::endl;
    a.interrupt();

    sleep(2);

    a->join();

    return 0;
}

当我用 g++ -std=c++11 main.cpp (gcc 4.9.2) 编译它时,我得到:

/usr/include/c++/4.9.2/functional:1665:61: error: no type named ‘type’ in ‘class std::result_of<interruptible<T>::interruptible(F&&, A&& ...) [with F = void (&)(); A = {}; T = std::thread]::<lambda(std::atomic_bool*, void (&)())>(std::atomic_bool*, void (*)())>’
       typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                             ^
/usr/include/c++/4.9.2/functional:1695:9: error: no type named ‘type’ in ‘class std::result_of<interruptible<T>::interruptible(F&&, A&& ...) [with F = void (&)(); A = {}; T = std::thread]::<lambda(std::atomic_bool*, void (&)())>(std::atomic_bool*, void (*)())>’
         _M_invoke(_Index_tuple<_Indices...>)
         ^

任何人都可以解决这个问题,我们将不胜感激!

【问题讨论】:

  • 清理您的代码:该错误可以从减少 90% 的代码中产生。最少、完整的示例,而不是“粘贴我的代码并询问如何修复它”。
  • 我将尝试将其浓缩为错误。不过,我的第一次尝试没有成功。如果您确切知道如何生成此错误,我们将不胜感激。
  • 如果您同意 @t.c.简化很好地反映了问题,修改问题以匹配它。请记住,我们更关心错误而不是系统。

标签: c++ multithreading c++11 lambda perfect-forwarding


【解决方案1】:

std::thread 存储传入其构造函数的参数的衰减副本。 对函数的引用经历了一个函数到指针衰减,这意味着对于DoStuff参数,传递给lambda的实际类型是void(*)(),而它的operator() 仍然期望衰减发生之前的类型参数,即void(&amp;)()(推导出F)。只要您尝试使用 A 参数包中的任何其他参数,就会出现同样的问题。

传递包装在std::ref 中的对象在这种情况下不是一个选项,因为引用包装器可能会超过存储它的实例的生命周期(您实际上并不知道ArgumentsFunction 是什么,这些可能是prvalue临时对象,其生命周期在调用interruptible的构造函数后结束)。当人们知道某个对象的生命周期时,有人决定在std::ref 中包装一些实例同时调用interruptible 的构造函数可能是合理的,但这里不是这种情况。

如果您想尽量减少将参数从 interruptible 传输到 lambda 表达式,然后再传输到 Function 对象所需的复制和移动次数,则可以选择重写构造函数,如下所示:

#include <type_traits>
// ...
template <typename F, typename... A>
interruptible(F&& Function, A&&... Arguments) :
    Interrupted(false),
    Thread(
        [](std::atomic_bool *Interrupted,
           typename std::decay<F>::type&& Function,
           typename std::decay<A>::type&&... Arguments)
        {
            LocalInterrupted = Interrupted;
            Function(std::move(Arguments)...);
        },
        &this->Interrupted,
        std::forward<F>(Function),
        std::forward<A>(Arguments)...
    )
{ }

【讨论】:

  • 现在线程函数只接受右值,不需要使用std::forward。这是你的意图吗?
  • @0x499602D2 std::forward 允许线程的构造函数通过恢复 ctor 调用中使用的表达式的值类别来使用完美转发复制/移动构造参数。然后 std::move()s 将这些副本复制到函数对象(在这种情况下为 lambda),因此它们可以由右值引用绑定,然后是 std::moveFunction。这是故意的,比较forward-versionnon-forward-version
  • 所以在内部它们被移动到线程函数的参数中?
  • @0x499602D2 线程对象调用函子 as-if 它是INVOKE(DECAY_COPY(std::forward&lt;F&gt;(f)), DECAY_COPY(std::forward&lt;Args&gt;(args))...),这意味着:1. 这些是利用完美转发的副本,2. 函子的参数总是右值
  • 如果仿函数的参数始终是右值,它们如何绑定到左值引用?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-30
  • 2019-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多