Too many copies when binding arguments to a variational pattern

I am creating a job queue. The task will be created in stream A, then the task will be sent to stream B, and stream B will complete the task. After the task is completed, the task will be sent back to stream A.

#include <functional> #include <iostream> #include <memory> using namespace std; template<typename T, typename... Args> class Job { public: Job(std::weak_ptr<T> &&wp, std::function<void(const Args&...)> &&cb) : _cb(std::move(cb)), _cbWithArgs(), _owner(std::move(wp)) {} public: template<typename... RfTs> void bind(RfTs&&... args) { // bind will copy args for three times. _cbWithArgs = std::bind(_cb, std::forward<RfTs>(args)...); } void fire() { auto sp = _owner.lock(); if (sp) { _cbWithArgs(); } } private: std::function<void(const Args& ...)> _cb; std::function<void()> _cbWithArgs; std::weak_ptr<T> _owner; }; struct Args { Args() = default; Args(const Args &args) { cout << "Copied" << endl; } }; struct Foo { void show(const Args &) { cout << "Foo" << endl; } }; int main() { using namespace std::placeholders; shared_ptr<Foo> sf (new Foo()); Args args; // Let say here thread A created the job. Job<Foo, Args> job(sf, std::bind(&Foo::show, sf.get(), _1)); // Here thread B has finished the job and bind the result to the // job. job.bind(args); // Here, thread A will check the result. job.fire(); } 

The above codes compile and work. But it gives the following results (g ++ 4.8.4 and clang have the same results):

 Copied Copied Copied Foo 

There are three copies! Unacceptable, I do not know where I was wrong. Why three copies? I googled and find the method from here: https://stackoverflow.com/a/166269/2126 , it only copies the parameters at a time. But it must initialize the bind function in the constructor.

Thank you, Peter Skotnitsky. Unfortunately, I don't have a C ++ 14 compiler. So, make Args movable:

 struct Args { Args() = default; Args(const Args &args) { cout << "Copied" << endl; } Args(Args &&) = default; Args& operator=(Args &&) = default; }; 

Now it copies only once :)

Finally, I took the codes from this thread https://stackoverflow.com/a/312947/ I want to say that the gen_seq pattern is a true ART.

+5
source share
1 answer

The first copy was made by std::bind :

 std::bind(_cb, std::forward<RfTs>(args)...) 

since he must keep broken copies of his arguments.

Two other copies are performed by the _cbWithArgs assignment, which is of type std::function (ยง 20.8.11.2.1 [func.wrap.func.con]):

 template<class F> function& operator=(F&& f); 

18 Effects: function(std::forward<F>(f)).swap(*this);

Where:

 template<class F> function(F f); 

9 [...] *this sets the copy of f initialized by std::move(f) .

That is, one copy for the constructor parameter, and another for storing the argument f .

Since Args is a non-transient type, an attempt to switch from the result of the call to std::bind returns to the copy.

In your code, it does not seem reasonable to store the results of the job in std::function . Instead, you can save these values โ€‹โ€‹in a tuple:

 template <typename T, typename... Args> class Job { public: Job(std::weak_ptr<T> wp, std::function<void(const Args&...)> &&cb) : _cb(std::move(cb)), _owner(wp) {} public: template<typename... RfTs> void bind(RfTs&&... args) { _args = std::forward_as_tuple(std::forward<RfTs>(args)...); } void fire() { auto sp = _owner.lock(); if (sp) { apply(std::index_sequence_for<Args...>{}); } } private: template <std::size_t... Is> void apply(std::index_sequence<Is...>) { _cb(std::get<Is>(_args)...); } std::function<void(const Args&...)> _cb; std::weak_ptr<T> _owner; std::tuple<Args...> _args; }; 

Demo

+5
source

Source: https://habr.com/ru/post/1233968/


All Articles