By-ref 参数:这是 std::thread 和 std::bind 之间的不一致吗?

By-ref arguments: is this an inconsistency between std::thread and std::bind?

本文关键字:std 不一致 之间 参数 这是 thread By-ref bind      更新时间:2023-10-16

std::bindstd::thread共享一些设计原则。由于它们都存储与传递的参数相对应的本地对象,因此如果需要引用语义,我们需要使用std::refstd::cref

void f(int& i, double d) { /*...*/ }
void g() {
int x = 10;
std::bind(f, std::ref(x), _1) (3.14);
std::thread t1(f, std::ref(x), 3.14);
//...
}

但我对最近的个人发现很感兴趣:std::bind将允许您在上述情况下传递一个值,即使这不是人们通常想要的。

std::bind(f, x, _1) (3.14); // Usually wrong, but valid.

但是,对于std::thread来说并非如此。以下内容将触发编译错误。

std::thread t2(f, x, 3.14); // Usually wrong and invalid: Error!

乍一看,我认为这是一个编译器错误,但该错误确实是合法的。由于30.3.1.2强加的复制衰减要求(int中的转换int&),std::thread构造函数的模板化版本似乎无法正确推导出参数。

问题是:为什么不需要类似于std::bind的论点呢?还是有意出现这种明显的不一致?

注意:在下面的评论中解释了为什么它没有重复。

bind返回的函数对象是为重用而设计的(即多次调用);因此,它必须将其绑定参数作为左值传递,因为您不想从所述参数中移动,否则以后的调用将看到从绑定参数中移出。(同样,您也希望函数对象作为左值调用。

这种担忧不适用于std::thread和朋友。线程函数只会使用提供的参数调用一次。离开它们是完全安全的,因为没有其他东西会看着他们。它们实际上是临时副本,仅为新线程制作。因此,函数对象作为右值调用,参数作为右值传递。

由于lambdas的存在,std::bind在到达时几乎已经过时了。 随着 C++14 项改进和 C++17std::applybind的剩余用例几乎消失了。

即使在 C++11 中,bind解决了 lambda 无法解决的问题的情况,这种情况也相对罕见。

另一方面,std::thread正在解决一个稍微不同的问题。 它不需要bind的灵活性来"解决每个问题",相反,它可以阻止通常很糟糕的代码。

bind的情况下,传递给f的引用不会x而是对x的内部存储副本的引用。 这是非常令人惊讶的。

void f(int& x) {
++x;
std::cout << x << 'n';
};
int main() {
int x = 0;
auto b = std::bind(f, x);
b();
b();
b();
std::cout << x << 'n';
}

指纹

1
2
3
0

其中最后一个0是原始x,而123是存储在f中的x的递增副本。

使用 lambda,可以清楚地了解可变存储状态和外部引用之间的区别。

auto b = [&x]{ f(x); };

auto b = [x]()mutable{ f(x); };

其中一个副本x然后反复调用f,另一个将x引用传递给f

确实没有办法在不允许f访问存储的x副本作为参考的情况下使用bind做到这一点。

对于std::thread,如果你想要这种可变的本地复制行为,你只需使用lambda。

std::thread t1([x]()mutable{ f(x); });

事实上,我认为 C++11 中的大多数 INVOKE 语法似乎是语言中没有 C++14 次幂 lambda 和std::apply的遗产。 很少有 lambda 和std::apply无法解决的情况(需要应用,因为 lambda 不容易支持将包移动到其中然后将它们扩展到内部)。

但是我们没有时间机器,所以我们有这些多种并行的方式来表达在C++的特定上下文中调用某些东西的想法。

据我所知,thread开始时的规则与bind基本相同,但在 2010 年由 N3090 修改以接受您确定的约束。

用它来将各种贡献一分为二,我相信您正在寻找 LWG 问题 929。

具有讽刺意味的是,其意图似乎是使thread构造函数不受约束。当然没有提到bind,尽管这个措辞后来也适用于async(LWG 1315之后的"清理"部分),所以我会说bind被抛在了后面。

不过,很难确定,所以我建议问委员会本身。