2

I was working with boost::bind (Boost 1.64.0 and gcc 8.3.0) to create a callable object and noticed an interesting feature An object passed in bind constructor as an argument to a wrappable function is copied multiple times. Even if the object is wrapped in std::move(). Although std::bind works as expected. Example:

#include <iostream>
#include <boost/bind.hpp>
#include <functional>

class Test
{
public:
    Test()
    {
        std::cout << "Create\n";
    }

    Test(const Test& rhs)
    {
        std::cout << "Copy\n";
    }

    Test(Test&& rhs)
    {
        std::cout << "Move\n";
    }

    ~Test() noexcept
    {

    }
};

void foo(Test& t)
{

}


int main()
{
    Test t;
    auto f = boost::bind(&foo, t);
    f();
}

Output for boost::bind(&foo, t);

Create
Copy
Copy
Copy
Copy
Copy

Output for boost::bind(&foo, std::move(t));

Create
Move
Copy
Copy
Copy
Copy

Output for std::bind(&foo, t);

Create
Copy

Output for std::bind(&foo, std::move(t));

Create
Move
  • Why does boost copy so many times?
  • Is it correct to pass rvalue as an argument to bind (in both cases of implementation)?
  • Do I understand correctly that bind will move the object to its context and store it, and when foo is called, passes it as an lvalue reference?

Thank you!

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207

1 Answers1

2

That's by design.

To avoid it, avoid copies of the bind adaptor and use ref:

auto f = boost::bind(&foo, boost::ref(t));

  • Why does boost copy so many times?

Mostly due to the fact that your constructor cannot be elided. Keep it an aggregate or trivial constructor and it doesn't happen.

  • Is it correct to pass rvalue as an argument to bind (in both cases of implementation)?

Yes, bind captures arguments by value (unless you use explicit ref() or cref() to create reference_wrappers.)

  • Do I understand correctly that bind will move the object to its context and store it, and when foo is called, passes it as an lvalue reference?

Yes


Demo

Live On Coliru

#include <boost/bind.hpp>
#include <functional>
#include <iostream>

struct Test {
    Test()                       { std::cout << "Create\n"; } 
    Test(const Test& /*unused*/) { std::cout << "Copy\n";   } 
    Test(Test&& /*unused*/)      { std::cout << "Move\n";   } 
    ~Test() noexcept             {                          } 
};

void foo(Test& /*unused*/) {}

int main() {
    Test t;
    auto f = boost::bind(&foo, boost::ref(t));
    f();
}

Prints:

Create
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Many thanks. But what if **t** is a local object, and **f** must be executed in another thread? – Залим Бесланеев Apr 24 '20 at 13:48
  • Then you can alleviate at least one copy by moving (`bind(&foo, std::move(t))`), and if you use types with constructors without weird side-effects, the compiler will optimize others. – sehe Apr 24 '20 at 13:52
  • Compare https://godbolt.org/z/3SVbT2 (you can see the compiler just emits all the `operator<<` because it /has/ to, but it's all inlined), vs. https://godbolt.org/z/dS0bRO. – sehe Apr 24 '20 at 13:58
  • It would be great if you can add why boost was designed in such a way. For me `std` is doing copy once. – abhiarora Apr 24 '20 at 13:59
  • Finally, you can get with the times: https://godbolt.org/z/ulFg0G or even just: https://godbolt.org/z/9NzU_j (`bind` is legacy and C++11 is a decade old). – sehe Apr 24 '20 at 14:00
  • @abhiarora Ah. In that respect, I do not know the answer (why is implementation X different from implementation Y). My *guess* is that the standard implementation is taking advantage of rvalue-semantics (because `std::bind` was only introduced in C++11, whereas Boost Bind predates it by many many years). – sehe Apr 24 '20 at 14:02
  • So, in comparison, std should be preferred to boost for bind? – abhiarora Apr 24 '20 at 14:09
  • 1
    Maybe. I'd say `std::bind` should not be used. Use the language feature instead (lambdas) so the compiler doesn't have to optimize through thick layers of library abstractions. – sehe Apr 24 '20 at 14:14