I'm creating a job queue. The job will be created in thread A, then the job will be sent to thread B and thread B will do the job. After the job has been done, the job will be sent back to thread 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's 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 compiles and works. But it gives the following results (g++ 4.8.4 & clang have the same results):
Copied
Copied
Copied
Foo
There are three copies! Not acceptable, I don't know where I did wrong. Why three copies? I googled and find a method from here: https://stackoverflow.com/a/16868401, it only copys params for one time. But it has to initialize the bind function in constructor.
Thanks, Piotr Skotnicki. Unfortunately, I don't have a C++14 compiler. So, let's make the Args movable:
struct Args
{
Args() = default;
Args(const Args &args)
{
cout << "Copied" << endl;
}
Args(Args &&) = default;
Args& operator=(Args &&) = default;
};
Now, it copys just one time :)
Finally, I adopted the codes from this thread https://stackoverflow.com/a/16868151/5459549. The template gen_seq is true ART, I must say.
The first copy is made by the std::bind
itself:
std::bind(_cb, std::forward<RfTs>(args)...)
as it needs to store decayed-copies of its arguments.
The other two copies are made by the assignment to _cbWithArgs
which is of type std::function
(§ 20.8.11.2.1 [func.wrap.func.con]):
template<class F> function& operator=(F&& f);
18Effects:
function(std::forward<F>(f)).swap(*this);
where:
template<class F> function(F f);
9 [...]
*this
targets a copy off
initialized withstd::move(f)
.
That is, one copy for the parameter of a constructor, and another one to store argument f
.
Since Args
is a non-movable type, an attempt to move from a result of a call to std::bind
falls back to a copy.
It doesn't seem reasonable in your code to store the results of job execution in a std::function
. Instead, you can store those 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With