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

Lea*_*elo 14 c++ c++11

std::bindstd::thread分享一些设计原则.由于它们都存储了与传递的参数相对应的本地对象,因此我们需要使用std::ref或者std::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);
    //...
}
Run Code Online (Sandbox Code Playgroud)

但我对最近的个人发现很感兴趣:std::bind即使这不是人们通常想要的,也会允许你在上面的案例中传递一个值.

    std::bind(f, x, _1) (3.14); // Usually wrong, but valid.
Run Code Online (Sandbox Code Playgroud)

但是,事实并非如此std::thread.以下将触发编译错误.

    std::thread t2(f, x, 3.14); // Usually wrong and invalid: Error!
Run Code Online (Sandbox Code Playgroud)

乍一看,我认为这是一个编译器错误,但错误确实合法.看来的模板版本std::thread的构造函数是无法正确推断的参数,由于副本腐烂 要求(tranforming int&int通过30.3.1.2规定).

问题是:为什么不要求类似于std::bind论点的东西?或者这是否明显不一致?

注意:解释为什么它在下面的评论中不重复.

T.C*_*.C. 9

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

这种担忧不适用于std::thread朋友.线程函数只能使用提供的参数调用一次.离开它们是完全安全的,因为没有别的东西可以看着它们.它们实际上是临时副本,仅用于新线程.因此,函数对象被称为rvalue,参数作为rvalues传递.

  • ^^这个.并且由于第一段中的原因,您可以使用`std :: bind`的结果作为有状态函数对象,因为它具有可以传递给目标对象并进行修改的非静态数据成员,并保留状态在电话之间.可变lambda对于由copy捕获的变量具有类似的属性.这是设计而不是`std :: bind`中的缺陷. (2认同)

Yak*_*ont 6

std::bind由于lambda的存在而到达时,它大部分已经过时了.随着C++ 14的改进和C++ 17 std::apply,剩下的用例bind几乎消失了.

即使在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';
}
Run Code Online (Sandbox Code Playgroud)

版画

1
2
3
0
Run Code Online (Sandbox Code Playgroud)

其中最后一个0是原来的x,而1 23是递增副本x内存储f.

使用lambda,可以明确可变存储状态和外部引用之间的差异.

auto b = [&x]{ f(x); };
Run Code Online (Sandbox Code Playgroud)

VS

auto b = [x]()mutable{ f(x); };
Run Code Online (Sandbox Code Playgroud)

其中一个副本x然后f重复调用它,另一个副本传递给xinto f.

bind如果不允许f访问存储的副本x作为参考,真的没有办法做到这一点.

因为std::thread,如果你想要这个可变的本地拷贝行为,你只需要使用lambda.

std::thread t1([x]()mutable{ f(x); });
Run Code Online (Sandbox Code Playgroud)

事实上,我认为C++ 11中的大多数INVOKE语法似乎都是没有C++ 14权力lambda和std::apply语言的遗产.很少有案例没有通过lambda解决std::apply(并且需要应用,因为lambda不能轻易地支持移动包进入它们然后在里面取出它们).

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

  • 当然,我同意。但是看...你需要一个不同的 lambda。这就是库组件的用途。并不是说 std::bind 是完美的,但这就是我们得到的。 (2认同)

Lig*_*ica 6

据我所知,thread从基本相同的规则开始bind,但是在2010年被N3090修改为接受你已经确定的约束.

使用它来平分各种贡献,我相信你正在寻找LWG问题929.

具有讽刺意味的是,目的似乎是使thread构造函数受到较少限制.当然没有提及bind,虽然这个措辞后来也适用于async(LWG 1315之后的"清理"部分),所以我会说bind落后了.

但是,很难确定,所以我建议问委员会本身.