What is the rationale behind std::bind and std::thread always copying arguments?

前端 未结 5 1294
你的背包
你的背包 2021-02-20 03:35

It\'s pretty well known that the default behaviour of std::bind and std::thread is that it will copy (or move) the arguments passed to it, and to use reference semantics we will

相关标签:
5条回答
  • 2021-02-20 04:09

    For both std::bind and std::thread, the invocation of the function on the given arguments is deferred from the call site. In both cases, exactly when the function will be called is simply unknown.

    To forward the parameters directly in such a case would require storing references. Which may mean storing references to stack objects. Which may not exist when the call is actually executed.

    Oops.

    Lambdas can do it because you're given the ability to decide, on a per-capture basis, whether you want to capture by reference or value. Using std::ref, you can bind a parameter by reference.

    0 讨论(0)
  • 2021-02-20 04:16

    I did in fact write a small utility that creates a delayed invocation functor (somewhat std::bind-like, but without the nested bind expressions/placeholders features). My main motivation was this case that I found counter-intuitive:

    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();
    

    The reason for that adaptor (which moves its lvalue ref argument to sink) is that the call wrapper return by std::bind always forwards the bound arguments as lvalues. This wasn't a problem with e.g. boost::bind in C++03 since that lvalue would either bind to a reference argument of the underlying Callable object or to a value argument via a copy. Doesn't work here since pointer_type is move-only.

    The insight that I got is that there really are two things to consider: how the bound arguments should be stored, and how they should be restored (i.e. passed to the Callable object). The control that std::bind grants you is as follows: arguments are either stored in a shallow (via the use of std::ref) or regular manner (using std::decay with perfect forward); they are always restored as lvalues (with cv-qualifiers inherited from the owning call wrapper). Except that you can bypass the latter with a small on-site adaptor lambda expression like I just did.

    It's arguably a lot of control and a lot of expression for relatively little to learn. In comparison my utility has semantics like bind(f, p) (decay and store copy, restore as lvalue), bind(f, ref(p)) (store shallowly, restore as lvalue), bind(f, std::move(p)) (decay and store from move, restore as rvalue), bind(f, emplace(p)) (decay and store from move, restore as lvalue). This feels like learning an EDSL.

    0 讨论(0)
  • 2021-02-20 04:21

    The most probable reason is simply that C++ uses value semantics by default pretty much everywhere. And using references could easily create issues concerning the lifetime of the referred to object.

    0 讨论(0)
  • 2021-02-20 04:22

    make_shared forwards to a constructor that is being called now. If the constructor uses call by reference semantics, it will get the reference; if it does call by value, it will make a copy. No problem here either way.

    bind creates a delayed call to a function that is called at some unknown points in the future, when the local context is potentially gone. If bind were using perfect forwarding, you would have to copy the arguments that are normally sent byreference and not known to be live at the time of the actual call, store them somewhere, and manage that storage. With the current semantics bind does it for you.

    0 讨论(0)
  • 2021-02-20 04:27

    std::bind creates a callable which is detached from the call site of std::bind, thus it makes much sense to capture all arguments by value by default.

    The general use case is to be identical to passing a function pointer to a function without knowing where it might end up.

    Lambdas give more flexibility for the programmer to decide whether the lambda will live beyond the scope arguments are captured from.

    0 讨论(0)
提交回复
热议问题