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

C++ std::bind和std::thread总是复制参数背后的基本原理是什么?,c++,stl,c++11,C++,Stl,C++11,众所周知,std::bind和std::thread的默认行为是复制(或移动)传递给它的参数,要使用引用语义,我们必须使用引用包装器 有人知道为什么这是一种良好的默认行为吗?特别是在具有右值引用和完美转发的C++11中,在我看来,完全转发参数更有意义 std::make_shared虽然并不总是复制/移动,但只是完美地转发提供的参数。为什么这里有两种看似不同的转发论点的行为?(总是复制/移动的std::thread和std::bind与不复制/移动的std::make_共享) 最有可能的原因是,

众所周知,std::bind和std::thread的默认行为是复制(或移动)传递给它的参数,要使用引用语义,我们必须使用引用包装器

  • 有人知道为什么这是一种良好的默认行为吗?特别是在具有右值引用和完美转发的C++11中,在我看来,完全转发参数更有意义

  • std::make_shared虽然并不总是复制/移动,但只是完美地转发提供的参数。为什么这里有两种看似不同的转发论点的行为?(总是复制/移动的std::thread和std::bind与不复制/移动的std::make_共享)


  • 最有可能的原因是,C++默认值到处都是使用值语义。使用引用很容易产生与引用对象的生存期有关的问题。

    std::bind创建了一个可调用的对象,该对象与
    std::bind
    的调用站点分离,因此默认情况下按值捕获所有参数非常有意义

    一般使用情形与将函数指针传递给函数相同,而不知道函数的最终位置


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

    make_shared
    转发给正在调用的构造函数。如果构造函数使用引用调用语义,它将获得引用;如果它确实按值调用,它将创建一个副本。这两种方式都没问题


    bind
    创建对函数的延迟调用,当本地上下文可能消失时,该函数将在将来的一些未知点调用。如果
    bind
    使用完美转发,则必须复制通常通过引用发送且在实际调用时未知为活动的参数,将其存储在某个位置,并管理该存储。使用当前语义
    bind
    为您执行此操作。

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

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

    哎呀


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

    事实上,我编写了一个小实用程序,创建了一个延迟调用functor(有点像
    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();
    
    使用指针\u type=std::unique\u ptr;
    指针类型源();
    空水槽(p型指针);
    指针类型p=source();
    //调用绑定()时,当前或以后无效
    //自动绑定=std::bind(sink,std::move(p));
    自动绑定=标准::绑定(
    [](指针类型&p){sink(std::move(p));}
    ,std::move(p));
    绑定();
    
    该适配器(将其左值ref参数移动到
    sink
    )的原因是
    std::bind
    返回的调用包装器总是将绑定参数作为左值转发。例如C++03中的
    boost::bind
    ,这不是问题,因为该左值要么绑定到底层可调用对象的引用参数,要么通过副本绑定到值参数。此处不起作用,因为
    指针\u类型
    仅用于移动

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


    这可以说是一个控制和表达的过程,相对来说,要学的东西很少。相比之下,我的实用程序的语义类似于
    bind(f,p)
    (衰减和存储副本,还原为左值),
    bind(f,ref(p))
    (浅层存储,还原为左值),
    bind(f,std::move(p))
    (衰减和存储自移动,还原为右值),
    bind(f,安置(p))
    (衰减和存储自移动,还原为左值)。这就像学习EDSL一样。

    如果将引用绑定到本地,则会遇到麻烦。如果调用的函数不存在于本地范围之外,则不会遇到麻烦。C++中有很多东西可以让你的脚被踢掉。我的问题更重要的是,这种行为似乎不那么直观。在某种程度上,我的回答包含了一个“并且该局部超出了范围”,但显然它在编辑后被删除了:但是程序员知道自己最想要什么,这不是C++的原则吗?在悬空引用的情况下,程序员应该小心,如果它愿意的话,它的函数的参数将是按值的,并且当我们的函数的参数接受引用时,我希望无论发生什么风险,都能完美地将其作为引用转发。只是想一想,不。原则是程序员不为他不使用的特性支付性能费用。“知道你在做什么”的东西只是实现这一目的的手段,而不是目的本身。C++有很多原则:-。其中最基本的一点是,它默认使用值语义;您必须明确请求引用