std :: bind和std :: thread总是复制参数的基本原理是什么?

时间:2012-05-08 17:14:14

标签: c++ stl c++11

众所周知,std :: bind和std :: thread的默认行为是它将复制(或移动)传递给它的参数,并且使用引用语义我们将不得不使用引用包装器。 / p>

  1. 有谁知道为什么这会产生良好的默认行为? ESP。在带有右值引用和完美转发的C ++ 11中,在我看来,完全转发参数更有意义。

  2. std :: make_shared虽然并不总是复制/移动,但只是完全转发提供的参数。为什么这里有两种看似不同的转发参数行为? (std :: thread和std :: bind总是复制/移动vs std :: make_shared但不是)

5 个答案:

答案 0 :(得分:13)

make_shared转发到现在名为 的构造函数。如果构造函数使用call by reference语义,它将获得引用;如果它按值调用,它将复制。这两种方式都没问题。

bind创建对函数的延迟调用,该函数在将来某些未知点处被调用,此时本地上下文可能已消失。如果bind正在使用完美转发,则必须复制通常由参考发送的参数,并且在实际调用时不知道它们是否存在,将它们存储在某处,并管理该存储。使用当前语义bind为您完成。

答案 1 :(得分:7)

对于std::bindstd::thread,对给定参数的函数调用都是从调用站点推迟的。在这两种情况下,确切地调用函数的时间都是未知的。

在这种情况下直接转发参数需要存储引用。这可能意味着存储对堆栈对象的引用。实际执行呼叫时可能不存在。

糟糕。

Lambdas可以做到这一点,因为您有能力在每次捕获的基础上决定是否要通过引用或值进行捕获。使用std::ref,您可以通过引用绑定参数。

答案 2 :(得分:2)

最可能的原因很简单,C ++默认使用值语义几乎无处不在。使用引用很容易产生与引用对象的生命周期有关的问题。

答案 3 :(得分:1)

std :: bind创建一个可调用的,它与std::bind的调用站点分离,因此默认情况下按值捕获所有参数是很有意义的。

一般用例与将函数指针传递给函数完全相同,但不知道它可能会在何处结束。

Lambdas为程序员提供了更大的灵活性来决定lambda是否会超出从中捕获的范围参数。

答案 4 :(得分:1)

实际上,我确实编写了一个小实用程序来创建一个延迟调用函数(有点像std::bind - 但是没有嵌套的绑定表达式/占位符功能)。我的主要动机是这种情况,我发现反直觉:

using pointer_type = std::unique_ptr<int>;
pointer_type source();
void sink(pointer_type p);

pointer_type p = source();

// Either not valid now or later when calling bound()
// auto bound = std::bind(sink, std::move(p));
auto bound = std::bind(
    [](pointer_type& p) { sink(std::move(p)); }
    , std::move(p) );
bound();

该适配器(将其lvalue ref参数移动到sink)的原因是std::bind返回的调用包装器总是将绑定参数转发为lvalues。例如,这不是问题。 C ++ 03中的boost::bind,因为该左值将绑定到底层Callable对象的引用参数或通过副本绑定到value参数。由于pointer_type仅限移动,因此无效。

我得到的见解是,有两件事需要考虑:绑定的参数应该存储,以及它们应该如何恢复(即传递给Callable对象)。 std::bind授予您的控件如下:参数存储在浅层(通过使用std::ref)或常规方式(使用std::decay完美转发);它们总是作为左值恢复(cv-qualifiers从拥有的调用包装器继承)。除了你可以使用像我刚才那样的小型现场适配器lambda表达式绕过后者。

对于相对较少的学习,可以说是很多控制和很多表达。相比之下,我的实用程序有语义,如bind(f, p)(衰变和存储副本,还原为左值),bind(f, ref(p))(浅存储,还原为左值),bind(f, std::move(p))(从移动中恢复和存储,恢复作为右值),bind(f, emplace(p))(从移动中衰减和存储,恢复为左值)。这感觉就像学习EDSL一样。

相关问题