为什么 std::thread() 按值传递参数(以及为什么 Stroustrup 博士给出的原因是不正确的)?

ale*_*833 7 c++ reference stdthread

引自 The C++ Programming Language(作者:Bjarne Stroustrup),第1213页

\n
\n

线程构造函数是variadic templates(\xc2\xa728.6)。这意味着要将引用传递给线程构造函数,我们必须使用引用包装器 (\xc2\xa733.5.1)。

\n

例如:

\n
void my_task(vector<double>& arg);\nvoid test(vector<double>& v)\n{\n    thread my_thread1 {my_task,v};           //oops: pass a copy of v\n    thread my_thread2 {my_task,ref(v)};      // OK: pass v by reference\n    thread my_thread3 {[&v]{ my_task(v); }}; // OK: dodge the ref() problem\n    // ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

问题是variadic templates通过引用目标函数传递参数没有问题。

\n

例如:

\n
void g(int& t)\n{\n}\n\ntemplate <class... T>\nvoid f(T&&... t)\n{\n    g(std::forward<T>(t)...);\n}\n\nint main()\n{\n    int i;\n    f(i);\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

按值传递的唯一原因是标准要求在参数上std::thread使用。std::decay

\n

我对么?

\n

有人可以解释一下 Stroustrup 的这句话吗?

\n

j6t*_*j6t 10

默认情况下通过引用传递将是一个主要的问题:当线程访问局部变量时,它们很可能在线程运行时超出范围,并且它只会有悬空引用。为了使值的使用更安全,代码必须明确指定哪些变量可以通过引用以您展示的方式之一安全地访问。

  • 同意。但这不是Stroustrup给出的理由。我发现斯特劳斯特鲁普的引述是不正确且具有误导性的。我对么? (2认同)

use*_*522 6

问题在于,可变参数模板可以通过引用目标函数来传递参数。

当您有这种带有模板参数包的可变参数模板时T,您可以将函数参数编写为

T... t
Run Code Online (Sandbox Code Playgroud)

或作为

T&&... t
Run Code Online (Sandbox Code Playgroud)

如果采用前一种形式,则无法直接传递引用。T总是会推导出非引用类型。另外,您可能不想要这种形式,因为它总是意味着额外的复制/移动。

如果使用第二种形式,总会T&&推导出一个参考。它不可能推导为非引用类型,因此它将始终按引用传递。

然而,抽象地讲,std::thread的构造函数应该能够通过引用和按值方式将参数传递给新线程。因此,天真地说,这个接口无法同时启用这两者。推导的引用T&&始终作为引用传递,或者始终用于std::decay_t<T>从它们构造对象以传递。演绎中没有办法区分。

因此,支持两者的唯一解决方案是对类型本身是否想要按引用传递或按值传递进行编码T。您可以决定默认为按值传递并且包装器类型std::reference_wrapper<U>指示按引用传递,或者您可以决定默认为按引用传递并且按值传递需要某种value_wrapper<U>包装器类型。

前者具有更多的语义意义并且更安全,因此对于需要能够按值和按引用进行这种抽象传递的接口来说,总是在标准库中选择前者。