I was experimenting with std::async
and ended up with a code that looks like that :
class obj {
public:
int val;
obj(int a) : val(a) {
cout << "new obj" << endl;
}
~obj() {
cout << "delete obj" << endl;
}
};
void foo(obj a) {
this_thread::sleep_for(chrono::milliseconds(500));
cout << a.val << endl;
}
int main(int argc, int **args) {
obj a(5);
auto future = async(foo, a);
future.wait();
return 0;
}
and the result is :
new obj
delete obj
delete obj
delete obj
5
delete obj
delete obj
delete obj
I then tried to change void foo(obj a)
by void foo(obj &a)
:
new obj
delete obj
delete obj
delete obj
5
delete obj
delete obj
Why would 5 copies of my object be made for this simple code? I have to admit, I'm really confused. Would someone care to explain this?
Edit
I'm using VS2012
In your case, obj
is being copied:
std::async
.async
's internal call to std::bind
.void foo(obj a)
since it takes a
by value.Believe it or not, the number of copies has actually been reduced since VC10.
It is not at all uncommon to see a library (be it the standard library or another one) trigger a few more copies than you would expect on your types. And usually, there is not too much you can do about it in.
There are 2 things that people commonly do to prevent copies:
obj
by reference (or in your case, const ref since foo
does not modify obj
). This will require using std::ref
with async.obj
. This won't prevent temporaries from being constructed and destroyed, but it will give you a chance to optimize the process a bit.Note that in your bare example of an object that holds onto only one int
, it might actually be faster to copy rather than move or pass by reference.
Example for passing obj
by reference into async
:
void foo(const obj& a) {
this_thread::sleep_for(chrono::milliseconds(500));
cout << a.val << endl;
}
int main(int argc, int **args) {
obj a(5);
auto future = async(foo, std::cref(a));
future.wait();
return 0;
}
Example for defining a move constructor:
class obj
{
public:
/* ... */
obj(obj&& a) : val(move(a.val)) {
// It is good practice to 0 out the moved object to catch use-after-move bugs sooner.
a.val = 0;
}
/* ... */
};
a
is being copied during the bind phase. To avoid multiple copies of a, use move constructor
semantics:
Add a move ctor
to obj
:
class obj {
public:
...
obj(obj&& other) {
cout << "move obj" << endl;
val = std::move(other.val);
}
};
In main:
obj a(5);
auto future = async(foo, std::move(a));
...
This way, 5 instances of obj will still be created, but since async supports movable
objects, the same copy will be moved from instance to instance (for heavy objects this will be significant over copying the object around). So now the output should be:
new obj
move obj
move obj
move obj
move obj
delete obj
delete obj
delete obj
5
delete obj
delete obj
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