1

Often, a function constructs a complicated result, for example a vector of objects. In plain, unoptimized C++, this can easily lead to many intermediate copies. I wonder how I should construct such an object in order to avoid unnecessary copies.

The following function shows what I'd like to do and how one could naively implement it. The calls on db are just pseudocode to roughly show the structure of the function.

vector<Result> get_result(Query q){
    vector<Result> ret;
    db.start_query(q); 
    while(db.has_more()){
        int i = db.get_next_int("i");
        string s = db.get_next_str("s");
        Result result(i, s);
        ret.push_back(result); // copy on push
    }
    return ret; // copy on return
}

How should I implement that kind of function considering modern C++11 or newer, and move semantics? It's possible to change the signature of the function, so that the vector<Result> could become an input parameter that catches the results.

Michael
  • 7,407
  • 8
  • 41
  • 84
  • 4
    If you have the info, `ret.reserve(final_size)` might avoid copies with reallocation. – Jarod42 Feb 20 '18 at 12:55
  • 1
    Just guessing, but I would put my money on `start_query` costing a lot more than anything you can do in this code. – Bo Persson Feb 20 '18 at 13:01
  • 1
    There is no _"copy on return"_, `return ret;` statement selects move constructor of `vector`, since `ret` is treated as _rvalue_ here. See §12.8/32 of C++11 Standard for details. The only thing I would add is to `reserve` vector memory as Jarod42 suggested and switch to `emplace_back` to prevent copying `result`. – Daniel Langr Feb 20 '18 at 13:32
  • Pass and return by reference. This reduces the need to copy the contents of large structures. You could also pass using smart pointers. – Thomas Matthews Feb 20 '18 at 16:30
  • @ThomasMatthews What? Have you heard about copy elision and move semantics? OP's `get_result` function is a classic example where return-by-value is by no doubt the best practice. – Daniel Langr Feb 20 '18 at 17:01
  • @DanielLangr: Passing by reference does not require optimization techniques. Also, passing by reference is less work by the compiler. See Bathsheba's answer about RVO is not guaranteed. – Thomas Matthews Feb 20 '18 at 17:08
  • @ThomasMatthews See my comment below Bathesba's answer. Even if NRVO wasn't applied (which is unlikely with mainstream compilers), just two move constructor would be called. These two calls are very cheap and practically negligible with respect to other function code. I responded to your comment about _copy_. There is no copy **guaranteed** here when returning by value. – Daniel Langr Feb 20 '18 at 17:30

2 Answers2

1

All you can do is to use emplace_back to construct result in-place and thus avoid copy, also there probably will be no copy on return because of NRVO.

ret.emplace_back(i, db.get_next("s"));

And anyway, as size of vector in VS2017 is 32 bytes, so it is very lightweight and move operation is fast, thus if you work with databases that, probably, doesn't matter.

Yola
  • 18,496
  • 11
  • 65
  • 106
  • Just for clarification, this is NRVO, not RVO. NRVO is optional (even in C++17), however, it will be likely applied by all mainstream compilers. Even if not, move constructor of `vector` will be called twice, which is cheap. – Daniel Langr Feb 20 '18 at 14:06
  • @DanielLangr Thank you for the clarification. i changed RVO to NRVO. Also i made last paragraph clearer. But if you would like answer this question, you will earn some points) – Yola Feb 20 '18 at 14:13
  • 1
    I would just duplicate your answer :). Maybe, I would change _"there will be no copy"_ to _"there will **likely** be no copy"_, since, again, NRVO is not mandatory. – Daniel Langr Feb 20 '18 at 14:28
0

Named return value optimisation is not even guaranteed in C++17.

So if you want to be certain that no unnecessary value copies are taken, then pass the return vector by reference:

void get_result(Query q, vector<Result>& foo)

and write

foo = std::move(ret);

as the final statement.

I've been careful to avoid return std::move(ret): see When should std::move be used on a function return value?

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 4
    There would be no unnecessary copies even if NRVO wasn't applied. In `return ret;` statement, `ret` is treated as _rvalue_ and therefore move constructor would be called, which is fast. I would never prefer passing by reference in this case. – Daniel Langr Feb 20 '18 at 12:56