英文:
Why std::thread() passes arguments by value (and why the reason given by Dr. Stroustrup is incorrect)?
问题
引用自《C++编程语言》(Bjarne Stroustrup著)第1213页:
线程构造函数是可变模板(第28.6节)。这意味着要将引用传递给线程构造函数,我们必须使用引用包装器(第33.5.1节)。
例如:
void my_task(vector<double>& arg);
void test(vector<double>& v)
{
thread my_thread1 {my_task,v}; //糟糕:传递v的副本
thread my_thread2 {my_task,ref(v)}; // 正确:传递v的引用
thread my_thread3 {[&v]{ my_task(v); }}; // 正确:规避了ref()问题
// ...
}
问题在于“可变模板”没有问题将参数按引用传递给目标函数。
例如:
void g(int& t)
{
}
template <class... T>
void f(T&&... t)
{
g(std::forward<T>(t)...);
}
int main()
{
int i;
f(i);
return 0;
}
std::thread
之所以只传递值,是因为标准要求对参数使用std::decay
。
我正确吗?
请有人解释一下Stroustrup的这段引文。
英文:
Quoting from The C++ Programming Language
(by Bjarne Stroustrup), page 1213
> The thread constructors are variadic templates
(§28.6). This implies that to pass a reference to a
> thread constructor, we must use a reference wrapper (§33.5.1).
>
> For example:
>
> c++
>void my_task(vector<double>& arg);
> void test(vector<double>& v)
> {
> thread my_thread1 {my_task,v}; //oops: pass a copy of v
> thread my_thread2 {my_task,ref(v)}; // OK: pass v by reference
> thread my_thread3 {[&v]{ my_task(v); }}; // OK: dodge the ref() problem
> // ...
> }
>
The problem is that variadic templates
have no problem to pass arguments by reference to a target function.
For example:
void g(int& t)
{
}
template <class... T>
void f(T&&... t)
{
g(std::forward<T>(t)...);
}
int main()
{
int i;
f(i);
return 0;
}
The only reason that std::thread
passes by value is because the standard requires to use std::decay
on the arguments.
Am I correct?
Can somebody please explain this quote by Stroustrup?
答案1
得分: 9
默认情况下按引用传递将是一个重大隐患:当线程访问本地变量时,它们可能在线程运行时已经超出了作用域,这将导致悬挂引用。为了使值的使用更安全,代码必须明确指定哪些变量可以通过其中一种方式安全地访问。
英文:
Passing by reference by default would be a major foot-gun: when the thread accesses local variables, they may well be out of scope by the time that the thread runs, and it would have only dangling references. To make the use of values safer, the code must specify explicitly which variables are safe to access by reference in one of the ways that you showed.
答案2
得分: 5
问题在于可变模板没有问题将参数通过引用传递给目标函数。
当你拥有这种类型的可变模板,具有模板参数包T
时,你可以将函数参数写成
T... t
或者
T&&... t
如果你采用前一种形式,那么你不能直接传递引用。T
将始终被推导为非引用类型。此外,你可能不希望使用这种形式,因为它总是意味着额外的复制/移动。
如果你使用第二种形式T&&
,它将始终被推导为引用。它不可能被推导为非引用类型,因此它将始续以引用方式传递。
然而,抽象地说,std::thread
的构造函数应该能够将参数同时以引用和值的方式传递给新线程。因此,从幼稚的角度来看,这个接口不能同时实现这两种方式。要么对于T&&
推导的引用始终以引用方式传递,要么始终使用它们来构造std::decay_t<T>
对象以传递。在推导中无法区分这两种情况。
因此,唯一的解决方案是在类型T
本身中编码你是想要按引用传递还是按值传递。你可以决定默认情况下是按值传递,而包装类型std::reference_wrapper<U>
表示按引用传递,或者你可以决定默认情况下是按引用传递,而按值传递需要某种value_wrapper<U>
包装类型。
前一种方法在语义上更有意义,更安全,因此在标准库中始终选择这种方式,用于需要能够以抽象的方式同时传递值和引用的接口。
英文:
> The problem is that variadic templates have no problem to pass arguments by reference to a target function.
When you have a variadic template of this kind with a template parameter pack T
, you can write the function parameter either as
T... t
or as
T&&... t
If you take the former form, then you can't pass-by-reference directly. T
will always deduce to non-reference types. Also, you probably don't want this form as it always implies an extra copy/move.
If you use the second form T&&
will always deduce to a reference. There is no possibility for it to deduce to a non-reference type and so it will always pass-by-reference.
However, abstractly, std::thread
's constructor should be able to pass the arguments by-reference as well as by-value to the new thread. So, naively, this interface can't enable both. Either the deduced references for T&&
are always passed on as references or they are always used to construct std::decay_t<T>
objects from them to pass on. There is no way to distinguish that in deduction.
So the only solution to support both is to encode whether or not you want pass-by-reference or pass-by-value in the type T
itself. You could either decide that the default is pass-by-value and a wrapper type std::reference_wrapper<U>
indicates pass-by-reference, or you could decide that pass-by-reference is the default and pass-by-value requires some kind of value_wrapper<U>
wrapper type.
The former makes much more semantic sense and is safer, so that's what's always chosen in the standard library for interfaces that need to be able to do this abstract passing by-value as well as by-reference.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论