简单来说,std::ref
和std::cref
在这里做什么?我读了here的文档,但无法理解。如果有人能在下面的代码中解释一下就太好了!
我有一段代码
#include<bits/stdc++.h>
int X=0;
void thread_executor(int &X) {
//do nothing
}
int main() {
std::thread worker1(thread_executor, std::ref(X));
return 0;
}
但是如果我把创建线程的行改为
std::thread worker1(thread_executor, X);
不能用。这是怎么回事
2条答案
按热度按时间ahy6op9u1#
std::ref
生成适当类型的std::reference_wrapper
。引用 Package 器是指向被引用事物的指针,充当值类型,但也隐式转换为(或显式转换为)引用。
因此,引用 Package 器可以存储在向量中。
在你的例子中,std thread将参数存储在tuple中,然后在另一个线程上调用它。因为当调用可能在局部作用域死亡很久之后发生时,通过引用捕获参数肯定是不安全的,所以参数通过 value 捕获。此外,由于这些值在线程启动后被丢弃,因此它们作为右值而不是左值传递给线程函数。
右值是可以安全读取的东西,但不能保证它们会一直存在。左值是赋值有意义的东西,而赋值给被丢弃的东西通常是无意义的。这里的L和R来自C的赋值运算符(Left = Right)的左侧和右侧。
所以呢
这里,
X
被复制到一个临时元组中,然后副本作为右值传递给thread_exrcutor
。int
类型的右值不能绑定到int&
(也就是int
的左值引用),因此编译器会拒绝您的代码。当您创建一个
std::reference_wrapper<int>
并将其传入时,**类型的右值可以转换为int&
。所以代码被接受了。std::ref(X)
等价于std::reference_wrapper<int>(X)
;它只是为你推导出int
。std::cref
只注入一个const
;sts::cref(X)
是std::reference_wrapper<int const>(X)
。通过使用
std::ref
,您可以保证在这里正确地管理生存期。该语言和库的设计目的是使您在没有它的情况下遇到的生命周期错误不那么常见。ztyzrc3y2#
这里的基本问题是
std::thread
的构造函数不“知道”线程的初始函数有一个int&
参数。您可能会问,为什么它不够智能,无法看到
thread_executor
的签名是void (int&)
。在这种特殊情况下,它可以检测到它,但在更普遍的情况下,这是不可能的。这样做的原因是,与其将thread_executor
传递给std::thread
构造函数,不如传递一些具有多个operator()
重载的类对象。实际调用的特定函数将由重载解析确定。在当前的C中,没有办法以编程方式确定将调用哪个重载。因此,一般来说,std::thread
构造函数(假设它是用类似于普通C的东西实现的,并且不是很神奇的)不知道实际调用的函数的签名。然而,
std::thread
构造函数必须决定是通过值还是通过引用接受参数X
,而不知道初始函数想要什么。所以它做了最安全的事:它总是按值获取参数。这意味着X
,作为一个左值,将被复制,如果你传递了一个右值,它将被移动。然后将复制或移动的结果传递给线程的初始函数。因此,没有
std::ref
的代码是错误的。如果它确实编译了,那么reference参数将引用副本,而不是原始X
。为了防止这种情况,std::thread
构造函数将copy * 作为右值 * 传递给线程的初始函数。现在int& X
无法绑定到该副本。通过使用
std::ref(X)
,可以创建一个引用X
的std::reference_wrapper<int>
对象。这是一种特殊类型,有点像指针(在本例中,指向X
的指针),因此当它被复制时,它仍然指向原始的X
。并且可以转换为int&
,因此thread_executor
的引用参数可以从引用 Package 器初始化,并引用引用 Package 器指向的对象。