Jump to content
  • Advertisement
Sign in to follow this  
hydrogen

C/C++ Function styles for returning data as well as return code

This topic is 4100 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Often a function has to return data as well as a return code, but there are different function styles that can be used to do this. I always get stumped thinking about this for ages because I know it'll be a hassle to go back and change it later. So im looking hopefully for some consensus on the best function style to use so that I never have to think about this again. In the following example a function must return a list of items and also a return code that can indicate success or that a non exceptional error prevents a list being returned. Here are the three best methods (I can think) of doing it.
//#1
//Function returns data directly
vector<Item> list = myClass.getList()
//return code can then be obtained (if desired) through seperate function call
int rc = myClass.getLastReturnCode()

//#2
//Caller provides the location to store data
//Function returns the return code directly
int rc = myClass.getList(vector<Item>& list)

//#3
//Function returns both data and return code directly via structure
ListRet returnData = myClass.getList()
struct ListRet
{
  vector<Item>& list
  int returnCode
};

//Other methods? Can think of others but they are far worse



Personally I prefer #2 for it's simplicity (and in some cases efficiency), but on the otherhand it looks more natural to retrieve the the data on the left hand side of the function ...Stuff_I_Want = Stuff_I_Ask_For()...rather than it being "returned" as a parameter...Stuff_I_Ask_For( Stuff_I_Want )... As an extra, I called these different "function styles" in the first paragraph. That doesn't sound right. Is there a better phrase for this? Cheers

Share this post


Link to post
Share on other sites
Advertisement
Well, I can't say there is a "best". Due to the syntax of C++, they're all a bit akward, but still these are all valid options that are being used. I tend to use #2. Although #3 is the more semantically correct(if you want to return multiple values, return multiple values),the syntax for this is clumsy. Maybe you could use an std::pair<> instead of explicitly defining a new structure.

Share this post


Link to post
Share on other sites
There's always exceptions...

I severely dislike #1 , since they are useless for multi-threading.

#2 is the C way.

#3 is very complicated. You need to declare the result explicitly, since you cannot check in same line:
ListRet returnData = myClass.getList();
// cannot be:
list = myClass.getList().list; // how do I get return code?



Quote:
a non exceptional error prevents a list being returned.


So, getting calling method results in: true, false, FILE_NOT_FOUND and std::exception?

I dislike this due to mixing of responsibility. When I ask for a value, I expect to get it, or fail. But by adding another condition, things start to complicate. There's rarely need to apply return codes at such low level, except for critical events, which exceptions take care off.

There is no one-size-fits-all solution to this. But return codes strike me as too C-ish. If you're looking for design-friendly solution, then it's RAII + exceptions that will take care of everything.

Having unusual return codes or weird half-success conditions is a nightmare from long-term maintainance perspective.


What kind of non-exceptional conditions did you intend to solve with this? Because you're looking for a trade-off between design and "alleged" simplicity.

Share this post


Link to post
Share on other sites
For number 3 it should probably return the vector by value and not by reference. In general because of copy construction semantics and ownership, you should probably try using reference function arguments when it's not idiomatically common to use a object return value. (Such as operator overloads and the such.)

And for the record naming a vector "list" makes my head hurt.

Share this post


Link to post
Share on other sites
Aesthetically, I prefer to modify the data as a reference parameter and return the error/success code. ie. approach 2, accurately termed 'the C way' above.

HOWEVER... to encourage good programming practices, it's good to force people to pass the return code in as a parameter. Why? Because it makes it harder for someone to ignore the return codes. Every day I see people post on here about errors that they could have found if they hadn't just been discarding the return codes. If they're forced to declare a variable to collect that return code, they're hopefully more likely to look at it afterwards.

It's generally better to try and write functions that can't fail. It may seem like an odd suggestion, but a bit of refactoring usually allows it in most cases.

Share this post


Link to post
Share on other sites
Quote:
Original post by esuvs
Maybe this is also useful...

http://www.boost.org/libs/optional/doc/optional.html


This is a technical solution to one of return value problem.

A better question with regard to C++ (not C) would be finding if return value is needed at all, or if a more suitable and C++-like solution exists, that avoids the problem altogether.

Return codes are generally favored over exceptions due to perceived performance hit (applicable only to rare cases), or for some compatibility/legacy/platform/compiler issues with regard to them.

From design perspective however, exceptions are a very reasonable solution to the 2-return-values problem.

In some cases, a tri-bool is a valid solution as well. In OO, this gives you four states: Success, Failure, Error (+ exception), where exception is independent fail condition. But these should really be used with caution, since they are prone to abuse, and quickly add complexity to algorithms and libraries.

Share this post


Link to post
Share on other sites
std::pair<int, Error> divide(int a, int b){
if (a==0 || b==0) return new std::pair<int, Code>(0, new DivideByZeroError());
return new std::pair<int, Code>(a/b, new Okay());}



...blah. on second thought don't really like it at all.

Share this post


Link to post
Share on other sites
#2 is definitely the preferred choice of the 3 you suggested.

Think about it logically.

What values are REQUIRED to be handled by a client, such as things which they have ownership of and MUST later call some methods on or delete. These values should usually be returned in output style parameters, so the caller cannot sanely ignore them.

What values SHOULD be handled by a client all the time, such as the list a user requests when they call a method named "GetList" even when ownership changes hands. These can be returned in any way prefered. The return value, compound containers, out parameters, etc.

What values can or will often be ignored. These can be return values or output parameters, with a leaning toward output parameters since they are much easier to ignore, although in/out parameters that can accept a "null" to mean no interest are also sometimes useful.

What values will be used to make decisions, or are not compound. These can be used most simply as return values. If List.size() took an int, code would grow greatly from (if list.size() != 0 ... to int listSize, list.size(listSize), if(listSize != 0)) .... so that's usually good to reduce

What values are complex or compound. Those cannot usually be processed without storage by the client. (ie, manager.GetPlayers(), requires the client to hold onto the returned value to do most useful things, so it doesn't hurt TOO bad to become manager.GetPlayers(playerList)).

In general I favor:

return the value as a return value when there is only 1 value ... of any type.

return all main values as out parameters when there are more than 1 useful returns.

return the status / error / success code as a return value and everything else as an output parameter when such a code is needed.

throw exceptions for most uncommon errors to reduce the likelyhood of the above case.

don't throw exceptions for "errors" that are not exceptional. For instance a user not properly authenticating as login is NOT an exception, since it is 1 of the 2 expected and common primary branches.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!