Vector building optimization using std::move

Viewed 52

I'm building some std::vector<Obj> where Obj are large objects which can be move constructed and assigned (think eg of Obj being large vectors). The code is typically a loop such as

std::vector<Obj> v;
while (...) {
    Obj foo = some client code(...);
    // ... some complicated stuff modifying foo
    v.push_back(foo);
}

As you can see, foo is not needed after being pushed in the vector.

My questions are

  • Does is make sense to write

      v.push_back(std::move(foo));
    

    to indicate to the compiler that it can take the contents of foo.

  • if it does, is it actually needed ? Indeed the compiler might notice that foo is destructed right after being pushed so that it can be moved... Does actual compilers use those kinds of optimisation ?

2 Answers

The compiler is not allowed to call the move constructor or elide the objects without you explicitly moving (ie, casting to an rvalue reference; possibly by std move) except under very specific situations.

A) the object is a temporary unnamed object.

B) The object is being returned from a function.

There are a few other requirements, but neither of the above two apply here. So no conversion to move/elision allowed.

The complier can sometimes use the as-if rule to turn a copy and later destroy into a move, but that is very difficult and often fragile, so you should not ever rely on it.

Whether or not the compiler can optimize it depends on exactly what Obj looks like, what functions you call when doing "complicated stuff modifying foo", whether Obj has a move constructor, and so on. You should try to compile and benchmark the code and see for yourself, or use an online service like https://godbolt.org/ to see what assembler code is produced.

However, the best way is to avoid copying or moving altogether. You can do this by calling emplace_back() to construct an Obj directly in the vector, and then modify it in place, like so:

while (...) {
    v.emplace_back();
    Obj &foo = v.back();
    // ... some complicated stuff modifying foo
}

And you can write this slightly shorter with C++17:

while (...) {
    Obj &foo = v.emplace_back();
    // ... some complicated stuff modifying foo
}
Related