将const引用传递给临时/匿名lambda表达式作为std::thread构造函数的参数是安全的吗?

8

接着这个问题: can-a-temperary-lambda-by-passed-by-reference?

我有一个固定的代码片段:

// global variable
std::thread worker_thread;

// Template function
template <typename Functor>
void start_work(const Functor &worker_fn)  // lambda passed by const ref
{
    worker_thread = std::thread([&](){
        worker_fn();
    });
}

这被称为“这样做”:
void do_work(int value)
{
    printf("Hello from worker\r\n");
}

int main()
{
    // This lambda is a temporary variable...
    start_work([](int value){ do_work(value) });
}

这似乎可以工作,但我担心传递一个临时 lambda 到线程构造函数中,因为线程将运行,但函数 start_work() 将返回且临时 lambda 将超出其作用域。

然而,我查看了定义如下的 std::thread 构造函数:

thread() noexcept; (1) (自 C++11 起)

thread( thread&& other ) noexcept; (2) (自 C++11 起)

template< class Function, class... Args > explicit thread( Function&& f, Args&&... args ); (3) (自 C++11 起)

thread(const thread&) = delete; (4) (自 C++11 起)

所以我认为会调用构造函数 3:

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

我很困难地理解这里写的内容,但看起来好像是试图移动lambda&&,我认为对于临时变量来说这是可以的。

那么我在我的代码片段中所做的是危险的(即参考超出作用域),还是正确的(即临时移动,一切顺利)?或者两者都不是?

另一个选择就是传递我的值(复制一份),在这种情况下也不算太糟糕。


1
如承诺一样更新了。完整答案超出了我的薪资等级。 - Bathsheba
1
您有一个悬空引用。 - Passer By
5
这个问题询问是否可以将 const & 传递给 std::thread 中的临时对象,是可以的。但这不是你正在做的事情。问题在于你正在通过引用捕获一个临时对象到 lambda 表达式中,这不是同一件事情,也不适用于这种情况。 - François Andrieux
2
简单来说,Lambda 函数可能会被正确地移动,但它所捕获的东西不会。作为一个骗子,我认为 Lambda 不过是比较花哨的函数对象。 - Bathsheba
1
在这个例子中,您不需要使用任何lambda表达式。start_work(do_work);worker_thread = std::thread(worker_fn);执行相同的操作(两者都缺少do_work调用的value参数)。 - Caleth
显示剩余9条评论
2个回答

8

临时变量确实被移动了,但它是作为参数传递给std::thread的“内部”临时变量。

该临时变量持有对作为参数传递给start_work的“外部”临时变量的引用,其生命周期在start_work返回后结束。

因此,你的“内部”lambda对象持有对可能在执行期间存在或不存在的对象的引用,这非常不安全。


啊,好的...所以从“理论上”讲,我可以将内部捕获更改为[=]以通过复制进行捕获,这应该是安全的(只会有一份副本,但比两份要好)? - code_fodder
只要您确保所捕获的对象也没有捕获可能会悬空的引用,就可以了。 - molbdnilo

3
一个 lambda 是 C++ 中的匿名结构体。如果我们要将该片段转换为没有 lambda 的等效代码,它将变成:
template <typename Functor>
void start_work(const Functor &worker_fn)
{
    struct lambda {
        const Functor& worker_fn;
        auto operator()() const { worker_fn(); }
    };
    worker_thread = std::thread(lambda{worker_fn});
}

lambda有一个非基于栈的const引用作为成员,无论lambda对象本身是否被复制,一旦start_work返回,该引用将会失效。


这是因为我正在通过引用捕获(例如[&]),而我可以改为通过复制进行捕获[=]吗? - code_fodder
@code_fodder 是的。如果是按值捕获,那么它将是 Functor worker_fn; - Passer By

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接