In c++11, is there ever still a need to pass in a reference to an object that will accept the output of a function?
Asked Answered
M

3

5

Prior to C++11, if I had a function that operated on large objects, my instinct would be to write functions with this kind of prototype.

void f(A &return_value, A const &parameter_value);

(Here, return_value is just a blank object which will receive the output of the function. A is just some class which is large and expensive to copy.)

In C++11, taking advantage of move semantics, the default recommendation (as I understand it) is the more straightforward:

A f(A const &parameter_value);

Is there ever still a need to do it the old way, passing in an object to hold the return value?

Melleta answered 16/6, 2012 at 4:54 Comment(4)
I thought the default recommendation has always been "do what is convenient and go out of your way if you find it necessary"...Gilbertina
There never was such need, check out cpp-next.com/archive/2009/08/want-speed-pass-by-valueIntra
@K-ballo: There might have been a need pre-standard (though people tend not to acknowledge it, C++ existed almost 20 years before it was standardized). Though maybe not, I was just starting to learn C++ about the time of standardization (and didn't even know it was happening at the time), was there such a thing as copy elision before the standard? Was it utilized by popular compilers?Johnstone
@Benjamin Lindley: Granted, never is going to far.Intra
C
7

Others have covered the case where A might not have a cheap move constructor. I'm assuming your A does. But there is still one more situation where you might want to pass in an "out" parameter:

If A is some type like vector or string and it is known that the "out" parameter already has resources (such as memory) that can be reused within f, then it makes sense to reuse that resource if you can. For example consider:

void get_info(std::string&);
bool process_info(const std::string&);

void
foo()
{
    std::string info;
    for (bool not_done = true; not_done;)
    {
        info.clear();
        get_info(info);
        not_done = process_info(info);
    }
}

vs:

std::string get_info();
bool process_info(const std::string&);

void
foo()
{
    for (bool not_done = true; not_done;)
    {
        std::string info = get_info();
        not_done = process_info(info);
    }
}

In the first case, capacity will build up in the string as the loop executes, and that capacity is then potentially reused on each iteration of the loop. In the second case a new string is allocated on every iteration (neglecting the small string optimization buffer).

Now this isn't to say that you should never return std::string by value. Just that you should be aware of this issue and apply engineering judgment on a case by case basis.

Cephalo answered 16/6, 2012 at 13:19 Comment(0)
C
3

It is possible for an object to be large and expensive to copy, and for which move semantics cannot improve on copying. Consider:

struct A {
    std::array<double,100000> m_data;
};

It may not be a good idea to design your objects this way, but if you have an object of this type for some reason and you want to write a function to fill the data in then you might do it using an out param.

Chiquita answered 16/6, 2012 at 5:0 Comment(4)
I might consider using an out param, but only because I wouldn't want to allocate many of those on the stack. Constructing an object from function return value is as cheap as it gets on any sane compiler.Brink
@eq-: nobody asks you to allocate this on the stack, new f() is perfectly acceptable.Systematize
@matthieum. There's no way to by-pass stack allocation when a function returns an object by value.Brink
@eq-: not necessarily, because copy elision may kick in, in which case the object is built directly in the heap allocated area (I just checked with Clang that this is the case).Systematize
P
3

It depends: does your compiler support return-value-optimization, and is your function f designed to be able to use the RVO your compiler supports?

If so, then yes, by all means return by value. You will gain nothing at all by passing a mutable parameter, and you'll gain a great deal of code clarity by doing it this way. If not, then you have to investigate the definition of A.

For some types, a move is nothing more than a copy. If A doesn't contain anything that is actually worth moving (pointers transferring ownership and so forth), then you're not going to gain anything by moving. A move isn't free, after all; it's simply a copy that knows that anything owned by the original is being transferred to the copy. If the type doesn't own anything, then a move is just a copy.

Pewee answered 16/6, 2012 at 5:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.