2017-05-04 139 views
13

std::bindstd::thread分享一些设计原则。由于两者的存储与传递的参数本地OBJETS,我们需要为使用std::refstd::cref如果引用语义需要:By-ref参数:这是std :: thread和std :: bind之间的不一致吗?

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! 

乍一看我以为这是一个编译器错误,但错误确实是合法的。看起来模板版本std::thread的构造函数由于30.3.1.2强加的copy decaying要求(转换int&int)无法正确推导出参数。

问题是:为什么不需要类似std::bind的论点?或者这是显而易见的意图不一致?

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

+2

这不是重复的。 *这个问题正在解释*我*问题的前提。此外,它不会触及std :: thread。实际上,我所指的是其中一个注释的一个反例(关于std :: thread的构造函数 –

回答

8

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

这个问题不适用于std::thread和朋友。线程函数只会被提供参数调用一次。从他们身上移开是完全安全的,因为没有别的东西会看着他们。它们实际上是临时副本,仅用于新线程。因此函数对象被称为右值,参数作为右值传递。

+2

^^由于第一段的原因,你可以使用'std :: bind'的结果作为有状态的函数对象,因为它有非静态的数据成员,可以传递给目标对象并进行修改,在通话之间保持状态可变lambda表达式对于由副本捕获的变量具有类似的属性。这是设计而不是'std :: bind'中的缺陷。 –

6

std::bind由于lambda的存在而到达时大多已经过时。随着C++ 14的改进和C++ 17 std::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,而123x递增副本存储内f

使用lambda时,可变的存储状态和外部参考之间的差异可以变得清晰。

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

VS

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

其中之一的副本x然后调用f反复就可以了,其它的引用传递到xf

确实没有办法用bind来做到这一点,而不允许f作为参考访问存储的副本x

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

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

事实上,我认为大多数C++ 11的INVOKE语法似乎是没有C++ 14个lambda表达式功率,并在语言std::apply的遗产。 lambda和std::apply没有解决的情况很少(需要申请,因为lambda不容易支持移动包装,然后将它们放入其中)。

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

+1

std :: bind不是(直到今天)已经过时了),这就是为什么它已经在C++ 11中引入与lambdas一起,实际上已经被引入lambdas的过时了,像std :: mem_fn或std :: bind1st(std :: bind2nd)。事实上,std :: bind只是* C++中的部分函数应用的机制(https://en.wikipedia.org/wiki/Partial_application)。 –

+1

@LeandroT。C.Melo'[x](auto && ... args) - > decltype(auto){return f(x,decltype(args)(args)...); }'我只是部分地将'x'应用于'f'而不使用'bind'。更复杂的案例可以用在更一般的案例中。像绑定一组参数。 – Yakk

+1

有趣的解决方案。我认为这不会起作用(开箱即用)成员函数虽然...?无论如何,我应该说唯一的*库组件*。从某种意义上说,如果我们从一个“脱糖”的角度思考,我们可以考虑一堆“过时”的东西。 –

6

从我所知道的,thread开始时基本上与bind相同的规则,但在2010年修改了N3090以承担您确定的约束。

用它来平分各种贡献,我相信你正在寻找LWG issue 929。具有讽刺意味的是,其意图似乎已经使thread构造函数的约束更少了。当然,没有提及bind,虽然this wording was later also applied to async(LWG 1315之后的“清理”部分),所以我会说bind被抛在后面。

虽然这很难确定,所以我会建议询问the committee itself