2

In my current project I need to implement quite a few functions/methods that take some parameters and generate a collection of results (rather large). So in order to return this collection without copying, I can either create a new collection and return a smart pointer:

boost::shared_ptr<std::vector<Stuff> > generate();

or take a reference to a vector which will be populated:

void generate(std::vector<Stuff> &output);

Both approaches have benefits. The first clearly shows that the vector is the output of the function, it is trivial to use in a parallelized scenario, etc. The second might be more efficient when called in a loop (because we don't allocate memory every time), but then it is not that obvious that the parameter is the output, and someone needs to clean the old data from the vector...

Which would be more customary in real life (i.e. what is the best practise)? In C#/java I would argue that the first one, what is the case in C++?

Also, is it possible to effectively return a vector by value using C++11? What would the pitfalls be?

Grzenio
  • 35,875
  • 47
  • 158
  • 240
  • Note that if you pass a writeable reference to a function, it kind of is obvious that the function will write to it and it is an output. Otherwise, it would be a const reference. – Albert Feb 21 '13 at 14:17

5 Answers5

4

do correctness first, then optimize if necessary

with both move semantics and Return Value Optimization conspiring to make an ordinary function result non-copying, you would probably have to work at it to make it sufficiently inefficient to be worth optimization work

so, just return the collection as a function result, then MEASURE if you feel that it's too slow

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
2

You should return by value.

is it possible to effectively return a vector by value using C++11?

Yes, C++11 supports move semantics. You return a value, but the compiler knows it's a temporary, and therefore can invoke a special constructor (move constructor) that is especially designed to simply "steal the guts" of the returned object. After all, you won't use that temporary object anymore, so why copying it when you can just move its content?

Apart from this, it may be worth mentioning that most C++ compilers, even pre-C++11, implement (Named) Return Value Optimization, which would elide the copy anyway, incurring in no overhead. Thus, you may want to actually measure the performance penalty you (possibly) get before optimizing.

I think you should pass by reference, or return a shared pointer, only when you need reference semantics. This does not seem to be your case.

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Are you saying that `wtf omg(){ wtf it; return it; }` will call the move ctor ? – nurettin Feb 21 '13 at 14:43
  • @nurettin: If it has one, yes. Otherwise, it will call the copy ctor. But most likely it will call none of them because it will elide the copy/move (NRVO). – Andy Prowl Feb 21 '13 at 14:45
  • I am unable to reproduce your assertion [on ideone](http://ideone.com/on1jnr) and on g++ 4.7 with various -O flags and clang with various -O flags. Move ctor just doesn't get called. – nurettin Feb 21 '13 at 14:57
  • @nurettin: You did reproduce it. The compiler just performed NRVO, so it elided the call to the move constructor (it would be unlikely that you get `42` printed otherwise) – Andy Prowl Feb 21 '13 at 15:00
  • @nurettin: p.s.: To see this, try adding `-fno-elide-constructors` to the command line – Andy Prowl Feb 21 '13 at 15:01
  • yes, I had to disable NRVO to get to the move ctor. But now `wtf(wtf &&omg){ n= omg.n; std::cout<< "move ctor!\n"; }` is called twice :-/ – nurettin Feb 21 '13 at 15:05
  • @nurettin: First a temporary is constructed, then the returned object is destructed, then the result is constructed from the temporary. Therefore, you have two calls to the move constructor (you would have two calls to the copy constructor if you hadn't defined a move constructor). Try to define a destructor and let it print some output: you'll see the destructor is called between the two invocations of the move constructor. – Andy Prowl Feb 21 '13 at 15:07
  • @nurettin: [In this answer](http://stackoverflow.com/questions/15005075/c-destructor-being-called-in-overloaded-arithmetic-operators/15005154#15005154) I happen to explain what goes on. – Andy Prowl Feb 21 '13 at 15:08
  • So just returning by value doesn't equal perfect forwarding and isn't very efficient, which is why you mentioned the nrvo alternative. +2 for your thoughts! – nurettin Feb 21 '13 at 15:11
  • @nurrettin: Well, in general returning by value *is* efficient if a move constructor is written. Normally, a move constructor will just copy and assign pointers of individual members, so no expensive copy operations are involved. But yes, in most cases the compiler will just elide the call because that's even more efficient. – Andy Prowl Feb 21 '13 at 15:30
1

There is an alternative approach. If you can make your functions template, make them take an output iterator (whose type is a template argument) as argument:

tempalte<class OutputIterator>
void your_algorithm(OutputIterator out) {
    for(/*condition*/) {
        ++out = /* calculation */;
    }
}

This has the advantage that the caller can decide in what kind of collection he wants to store the result (the output iterator could for instance write directly to a file, or store the result in a std::vector, or filter it, etc.).

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
1

The best practise will probably be surprising to you. I would recommend returning by value in both C++03 and C++11.

  • In C++03, if you create a std::vector local to generate and return it, the copy may be elided by the compiler (and almost certainly will be). See C++03 §12.8/15:

    in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type, the copy operation can be omitted by constructing the automatic object directly into the function's return value

  • In C++11, if you create a std::vector local to generate and return it, the copy will first be considered as a move first (which will already be very fast) and then that may be elided (and almost certainly will be). See C++11 §12.8/31:

    in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

    And §12.8/32:

    When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

So return by value!

Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
1

Believe it or not, I'm going to suggest that instead of either of those approaches just take the obvious implementation and return by value! Compilers are very often able to optimize away the notional copy that would be induced, removing it completely. By writing the code in the most obvious manner you make it very clear to future maintainers what the intent is.

But let's say you try return by value and your program runs too slow and let's further suppose that your profiler shows that the return by value is in fact your bottleneck. In this case I would allocate the container on the heap and return as an auto_ptr in C++03 or a unique_ptr in C++11 to clearly indicate that ownership is being transferred and that the generate isn't keeping a copy of that shared_ptr for its own purposes later.

Finally, the series at http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ provides a great perspective on almost the exact same question.

Mark B
  • 95,107
  • 10
  • 109
  • 188