tags:

views:

236

answers:

8

I am curious about prevalent methodologies.

I found myself doing both of these things interchangeably:

Note in all cases the object is allocated locally.

std::string GetDescription ()
{
      std::string desc;

     /* Create a description */

       return desc;
}


void GetResult (map<int, double> & resultMap)
{
        /*Fill map result map with  values*/

}

map<int, double> GetResult2(map<int, double> & resultMap)
{
          map<int, double> & resultMap
        /* Fill map result map with  values */
          return resultMap;
}

What is the preferred way to do this?

+2  A: 

Either return it by value or manipulate it as an out parameter that is passed in. Don't return anything allocated on the stack via a reference unless you like crashes and undefined behavior.

EDIT: It really is a matter of style. I prefer values for value types and out parameters for non-value types. Although if I'm really in the mood to abstract things I use a specialized interface, something in the spirit of an ostream.

MSN
No sh***t, the question is about design and NOT potential bugs... thx
Don't be an idiot Sasha, MSN told you exactly what you need to know.
Ed Swangren
Of course, I agree that un-attantive parameter passing can result in this sort of errors, and it is important. It just not what I am after here..
I guess some people do NOT like my question and down-voting it...such an abuse of SO...
It was the tone more than the substance of the response that I had an issue with.
Ed Swangren
...But I was not the downvoter...
Ed Swangren
A: 

In general I favor the first approach, because it makes the value assignment the result of the function call rather than a "side effect" of it. I think this makes code easier to read.

However, when efficiency is paramount, or when the type is expensive to create/assign, I use the out parameter approach. For some types of objects (e.g., containers) using an out parameter is reasonably idiomatic, so that is a consideration as well.

zweiterlinde
That's what I am getting at, and object is heavy I don't want to carry over across function calls
+1  A: 
Ed Woodcock
I tend to do that for large object as well...
Why a bool? Wouldn't an exception be more appropriate? That's how the original function would failed as well.
MSalters
Well, exceptions aren't relevant in all cases, sometimes things can go wrong without throwing an error, for example if you get an unexpected number of values, or if some of the values are not what was expected. You could always put a throw() on the end if you want an exception =]
Ed Woodcock
The STL gives another schema for such operations (look at `map::insert`): return a `pair<ReturnType, bool>`.
Konrad Rudolph
A: 

C++ compilers can optimize certain situations involving returning a value. See here. I tend to prefer returning values, mostly for aesthetic purposes and because I prefer functional style, but obviously you need to be careful with objects, like collections, that might end up deep copied.

TrayMan
yea, something like return map<?,?> using const as a return type can do much optimization...But we cannot rely on that.
The article is quite outdated. Modern compilers perform NRVO in many more situations. In particular, nothing speaks against using temporaries as long as the execution path of the method defines only one object that is returned.
Konrad Rudolph
A: 

For value types returning is fine. For objects and containers pass by reference is the way to go. Returning deeply nested containers results in a lot of extra overhead for no benefit. A danger to returning non-value types is when you have pointer member variables. Do you want a deep or shallow copy of these members? If you're not careful you can end up with dangling pointers.

Aaron Saarela
Can you justify your overhead assertion? In my experience it's false.
Konrad Rudolph
+1  A: 

For larger objects I tend to return a smart pointer, replace std::auto_ptr with std::unique_ptr when C++0x is released and you got a compiler which support it, or another smart pointer of choice.

This pattern also conforms with the strong exception guarantee.

std::auto_ptr< std::map<int, double> > GetResult()
{
    std::auto_ptr< std::map<int, double> > result(new std::map<int, double>());
    // Fill result map with values
    return result;
}
dalle
I tried an experiment with your code snippet to see if auto_ptr would convert if I did "return new ..." (answer is no) and noticed that you missed writing "std::map" where you need them.
Zan Lynx
@Zan Lynx: Yes, std::auto_ptr constructor is explicit. Sorry about the namespace confusion, I took some code of the OP.
dalle
+1  A: 

A function interface has two parts:

  1. input, conferred via arguments
  2. output, conferred via return values

This schema is certainly not an absolute law but it's a very good rule, both for consistency (code aesthetics) and for usability. Don't break this schema unless you've got a very good reason.

Efficiency? Generally not a good reason. Returning a value via out parameter for efficiency is a pretty low-level hack, and it has got no place in the public interface of an API. Don't use it. In particular since it's an extremely premature optimization.

Modern C++ compilers perform named return value optimization routinely. Few situations cannot be optimized in that manner and I doubt whether these situations benefit from out arguments in general.

And in the few cases where such an out argument would be crucial, refactor the method into a public API method and a private implementation like so:

namespace impl {
    void my_private_impl(args…) {
        …
    }
}

LargeType my_public_function(args…) {
    LargeType ret;
    impl::my_private_impl(ret, args…);
    return ret;
}

That way, the client still profits from a clean interface and reaps the performance benefits (and, by the way, the outer method will be inlined anyway so there is no performance loss in using this pattern).

Konrad Rudolph
A: 

It greatly depends on the task at hand. I would go for the first approach for functions that create new values while using the second approach for functions that change the parameters. As mentioned by others, in some cases other restrictions (cost of creation / assignment) can affect the decision.

The first definition is clearly stating that it will create a new object out of nothing and will return it. That is, the same definition is stating the semantics of the call.

The second and third definitions require the caller to create an object before calling the function. This can be a hassle to the user as she cannot just ignore the return value (or use it as an unnamed temporal to feed another function, see snippet 1), and can be limited in cases (i.e. cannot reassign a reference from an already created object, see snippet 2).

It is not clear from the function definition what is the use of the parameter inside the function. Will the behavior of the function differ depending on the passed object? It is not clear from the function definition.

The third type is, I believe, the worst option. Not only it forces the caller to create a named variable to pass to the function but also makes a copy to return (unless it was a typo and you are returning a [possibly const] reference)

At any rate, other considerations can unbalance the advantage of clear semantics that the first approach provides. In some cases performance, or other limitations like objects that cannot be copied (see snippet 3)

// snippet 1
//    user code to the different approaches
class X {};
X f1();
void f2( X& );
X f3( X& );
void g( X const & );

void test1()
{
   g( f1() ); // valid code
}
void test2()
{
   X x; // must create instance before
   f2( x ); // then call the function
   g( x );
}
void test3()
{
   X x; // must create instance before
   g( f3( x ) );
}

// snippet 2
class Y {};
class X
{
public:
   X( Y & y ) : y_( y ) {}
private:
   Y & y_;
};
X f1();
void f2( X & );
X f3( X & );

void test1()
{
   X x = f1();
}
void test2or3()
{  
   Y y;
   X x( y ); // user must create with a reference
   f2( x ); // f2 cannot change the reference inside x to another instance
}

// snippet 3
class X
{
private:
   X( X const & );
   X& operator= ( X const & );
};
X f1(); // Returned object cannot be used
void f2( X & );
X f3( X & );

void test1()
{
   X x = f1(); // compilation error
}
void test2()
{
   X x;
   f2( x );
}
void test3()
{
   X x;
   X y = f3( x ); // compilation error
}
David Rodríguez - dribeas