Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::async and object copy

Tags:

c++

c++11

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

like image 682
Simon-Okp Avatar asked Feb 16 '13 15:02

Simon-Okp


2 Answers

In your case, obj is being copied:

  1. Twice by the call to std::async.
  2. Twice by async's internal call to std::bind.
  3. Once by the call to 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:

  1. Take obj by reference (or in your case, const ref since foo does not modify obj). This will require using std::ref with async.
  2. Define a move constructor for 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;
    }

    /* ... */
};
like image 121
Sean Cline Avatar answered Oct 06 '22 15:10

Sean Cline


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
like image 27
eladidan Avatar answered Oct 06 '22 15:10

eladidan