Related
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 7 years ago.
Improve this question
I'm wondering, is there a better way to write code when there are functions with status returns.
Below is an example. (Please ignore simple code errors if there are any. I'm specifically talking about the structure. Plus, I'm at work and don't have a compiler on this computer)
#include "Session.h"
Session::Session(const char * IPaddress, unsigned int openPort)
{
ssh_session mySession;
hostIP = IPaddress;
port = openPort;
}
int Session::cBeginSession()
{
try
{
int status = ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP);
if (status == 0)
{
status = ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY,
SSH_LOG_PROTOCOL);
if(status == 0)
{
status = ssh_options_set(mySession, SSH_OPTIONS_PORT, &port);
if (status == 0)
{
std::cout << "Session started\n";
return 0;
}
else
{
std::cout << "Unable to set port\n";
return -3;
}
}
else
{
std::cout << "Protocol option log verbosity unable to set\n";
return -2;
}
}
else
{
std::cout << "Unable to set Host address\n";
return -1;
}
}
catch (...)
{
std::cout << "Unknown exception occurred\n";
return -8;
}
}
I typically use if-else statements with the status parameters, but I tend to end up with large nests of if-else statements if there are more than one or two functions involved. Is there a more readable way to write something like this? It turns into a rats nest very quickly.
EDIT: Thank you for all the replies. I think I have some ideas of how to structure my code better. I appreciate all the diligent suggestions.
In modern C++ programming, typically, if you encounter an error where the program can't continue, then I think it's better to throw an exception.
So your function would wouldn't return anything (i.e. void). Whenever it ran into a can't continue situation, you would throw an exception that tells what the error is. The calling code would then deal with the error.
The advantage to this, is that you get to choose where to deal with the error. For example, the stack may unwind all the up to main.
You code could look like this:
void Session::cBeginSession()
{
if (ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP))
{
// throw an exception
}
if (ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY, SSH_LOG_PROTOCOL))
{
// throw an exception
}
if (ssh_options_set(mySession, SSH_OPTIONS_PORT, &port))
{
// throw an exception
}
}
Once you get the hang of coding with exceptions, code tends to be cleaner and more robust since you're not always worrying about checking return codes.
EDIT
To answer you comment. You can choose how and when to handle the error. You can just catch the exception above your call. But, in general, if you want to do something that can fail (but not end a program) you can make another function that returns a boolean status.
bool Session::tryCBeginSession()
Now, your original function void Session::cBeginSession() would be implemented in terms of this new function. I've found that in most cases writing these dual functions is done only in a limited number of cases.
I like to reduce nesting, like this:
status = fcn1();
if ( status == 0 )
{
// something good
status = fcn2();
}
else
{
// something bad happened. report, and leave status reporting failure.
}
if ( status == 0 )
{
// something good
status = fcn3();
}
else
{
// something bad happened. report, and leave status reporting failure.
}
if ( status == 0 )
{
// something good
status = fcn4();
}
else
{
// something bad happened. report, and leave status reporting failure.
}
I like that error printing is close to the error occurrence. Sure, when failure happens, the status gets checked extra times. But it's a small price to pay for the simplicity.
This also lends itself well to de-allocation of resources and closing files at the end, regardless of where the error occurs.
You don't need if-else if you either return or throw in the statement that follows (this is a good case for throwing BTW). Plain ifs will do.
The type of messages you're printing are usually better suited to stderr rather than stdout (cerr rather than cout).
If you decide you will keep using error statuses, symbolic constants (or enums or defines) are usually preferred over "magic numbers"
This is a pretty ideal scenario for exception-handling (at least how it was intended). Exception-handling is typically appropriate for handling external input errors, as in this case where the external input is coming from a socket.
You already have a try/catch block, but I would suggest eliminating it since there's no recovery code. Keep your try/catch blocks generally focused around areas where you make a change transaction. Catching an exception then rolls back the changes, gets the system back to a valid state, and possibly outputs some message.
Something like this:
void Session::cBeginSession()
{
if (ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP) != 0)
throw runtime_error("Unable to set Host address");
if (ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY, SSH_LOG_PROTOCOL) != 0)
throw runtime_error("Protocol option log verbosity unable to set.");
if (ssh_options_set(mySession, SSH_OPTIONS_PORT, &port) != 0)
throw runtime_error("Unable to set port");
std::cout << "Session started\n";
}
Let the client code calling this function catch the exception at a site where it's appropriate to handle and recover from the error. Just worry here about throwing the exception appropriately in the case of these external input errors.
Note that exception-handling is typically ultra cheap in the non-exceptional cases (where you don't throw) with optimizations like zero-cost EH. However, these kinds of exception-handling compiler optimizations make the rare case much slower where you actually do throw an exception. So exceptions should be used for truly exceptional cases resulting from some kind of external input your software can't normally handle, as in this case.
Another caveat relevant in certain types of larger systems (plugin architectures, e.g.), is that typically exceptions should not be thrown across module boundaries.
This is somewhat opinionated but I don't recommend having lots of catch branches based on the type of exception (as commonly found in Java, e.g.). Often there isn't a need to distinguish the actual type of an exception so much as relay a message to the user, e.g. Catch exceptions as generally/coarsely as you can, and keep the try/catch blocks to a minimum (high-level transaction-oriented mentality: transactions succeed as a whole or fail and roll back as a whole).
Otherwise exceptions can really simplify these kinds of cases, and a whole lot of the C++ library (and even parts of the language) throw exceptions normally (I really think C++ and exception-handling are inseparably tied), so it can be useful to make use of them since a robust program will typically need to generally catch them anyway.
In this particular case (error codes handling), I'm advocating for early returns:
int status = ssh_options_set(mySession, SSH_OPTIONS_HOST, &hostIP);
if (status != 0)
{
std::cout << "Unable to set Host address\n";
return -1;
}
status = ssh_options_set(mySession, SSH_OPTIONS_LOG_VERBOSITY,
SSH_LOG_PROTOCOL);
if (status != 0)
{
std::cout << "Protocol option log verbosity unable to set\n";
return -2;
}
status = ssh_options_set(mySession, SSH_OPTIONS_PORT, &port);
if (status != 0)
{
std::cout << "Unable to set port\n";
return -3;
}
std::cout << "Session started\n";
return 0;
I find the code much more readable because it has lest nesting and the error handling is kept close to the point where the error occured instead of being buried in a far else branch.
If you decide it is best to use exceptions rather than error codes, you can keep the same structure and replace returns with throws.
some suggestions noted in the code:
// improvement - derive your own descriptive exception FROM A STANDARD EXCEPTION TYPE
struct session_error : std::runtime_error
{
using std::runtime_error::runtime_error;
};
// improvement in constructor: initialiser lists
Session::Session(const char * IPaddress, unsigned int openPort)
: mySession()
, hostIP(IPaddress)
, port(openPort)
{
}
namespace {
// use of anonymous namespace so this wrapper function does not pollute other compilation units
void setopt(ssh_session& session, int opt, const void* val, const char* context)
{
if (ssh_options_set(session, opt, val))
throw session_error(context);
}
}
void Session::cBeginSession()
{
// improvement - defer to wrapper function that handles nasty return code logic
// and throws a sensible exception. Now your function is readable and succinct.
setopt(mySession, SSH_OPTIONS_HOST, &hostIP, "setting host option");
setopt(mySession, SSH_OPTIONS_LOG_VERBOSITY, SSH_LOG_PROTOCOL, "setting verbosity");
setopt(mySession, SSH_OPTIONS_PORT, &port, "can't set port");
std::cout << "Session started\n";
}
I'm looking at error testing and reporting techniques from function calls, especially when multiple functions are called. As an example of what I mean, for simplicity each function returns a bool:
success = false;
if (fnOne ())
{
if (fnTwo ())
{
if (fnThree ( ))
{
success = true;
}
else
{
cout << "fnThree failed" <<endl;
}
}
else
{
cout << "fnTwo failed" <<endl;
}
}
else
{
cout << "fnOne failed" <<endl;
}
I find with the above example (which I see everywhere) the code quickly becomes unreadable, especially when it calling code becomes multi-screen in height.
Currently my way of dealing with this in C++ (Including 'c' tag in case someone has a C technique which is smooth) I store a bool and a string in my object. The bool represents success/fail and the string represents a reason for the fail state. I call a function and if the function fails, the function internally sets the object into fail state and provides a string based reason. I'm still not 100% happy with this method... but its the best I have so far. Example of what it looks like:
void myobj::fnOne (void)
{
if (m_fluxCapacitorProngCount > 3)
{
setState (false, "myobj::fnOne - Flux capacitor has been breeding again");
}
}
void myobj::fnTwo (void)
{
if (m_answerToLifeUniverseAndEverything != 42)
{
setState (false, "myobj::fnTwo - Probability drive enabled?");
}
}
void myobj::setup (void)
{
// Ensure time travel is possible
if (valid())
{
fnOne ();
}
// Ensure the universe has not changed
if (valid())
{
fnTwo ();
}
// Error? show the reason
if (valid() == false)
{
cout << getStateReason () << end;
}
}
Where valid () returns true/false and getStateReason () returns the string provided in the function when the error occured.
I like that this grows without the need to nest the conditions, to me I find this more readable but I'm sure there are problems...
What is the best [cleanest] way to handle detecting and reporting multiple function call return conditions?
This code should be clearer than your first variant:
if (!fnOne ())
{
cout << "fnOne failed" <<endl;
return;
}
if (!fnTwo ())
{
cout << "fnTwo failed" <<endl;
return;
}
if (!fnThree ())
{
cout << "fnThree failed" <<endl;
return;
}
success = true;
In general, for C++ you can use exceptions for error handling.
If you really want one function to return a value that represents the success/failure of several other functions (and just that - not a generalized return value from each function, which would require some way of returning an array/tuple/vector of values), here's one approach:
int bigFunction()
{ int return_value = 0;
if (function1() != 0)
return_value |= (1 << 0);
if (function2() != 0)
return_value |= (1 << 1);
if (function3() != 0)
return_value |= (1 << 2);
// ....
return return_value;
}
The idea is to allocate one bit each in the return value to indicate success/failure of each sub-function. If your sub-functions have a small set of possible return values that you actually want to capture, you could use more than one bit per function - i.e. two bits would allow you four different values for that field.
On the other hand, something like this means you're probably either a) writing some pretty low-level code like a device driver or kernel or something or b) there is probably a better approach to solving the problem at hand.
Dealing with errors in your code (bugs) and errors arising out of user input is a huge topic on its own. The technique you employ depends on the complexity of your code and the expected life of the code. The error handling strategy you would employ for a homework project is less complex than the error handling strategy you would employ for a semester project, which will be less complex than the error handling strategy you would employ for an in-house project, which will be less complex than a project which will be widely distributed to clients.
Strategy 1: Write an error message and abort
The simplest error handling strategy, that you can employ in homework project, is write a message out to stdout and and then call abort().
void fun1(int in)
{
if (in < 0 )
{
printf("Can't work with a negative number.\n");
abort();
}
// Rest of the function.
}
Strategy 2: Set a global error code and return
The next level of error handling involves detecting a bad input and dealing with it without calling abort(). You could set a globally accessible error code to indicate the type of error. I would recommend using this approach for homework projects, semester projects, and projects that are exploratory in nature.
void fun2(int in)
{
if (in < 0 )
{
// Indicate that "fun2" came accross a NEGATIVE_INTEGER_ERROR.
setErrorCode(NEGATIVE_INTEGER_ERROR, "fun2");
return;
}
// Rest of the function.
}
void funUser(int in)
{
// Call fun2
fun2(in);
// If fun2 had any errors, deal with it.
if (checkErrorCode())
{
return;
}
// Rest of the function.
}
The next level of error handling involves detecting a bad input and dealing with it using other options. You could return an error code from the function. If you are using C++, you could throw an exception. Both these options are valid ways of dealing with large projects --- be they in-house or distributed for wider consumption. They are applicable to any project in which the user base is beyond the team of developers.
Strategy 3: Return an error code from the function
int fun3(int in)
{
if (in < 0 )
{
// Indicate that "fun3" came accross a NEGATIVE_INTEGER_ERROR.
return NEGATIVE_INTEGER_ERROR;
}
// Rest of the function.
}
void funUser(int in)
{
// Call fun3
int ecode = fun3(in);
// If fun3 had any errors, deal with it.
if (ecode)
{
return;
}
// Rest of the function.
}
Strategy 4: Throw an error code from the function (C++)
void fun4(int in)
{
if (in < 0 )
{
// Indicate that "fun4" came accross a NEGATIVE_INTEGER_ERROR.
throw NEGATIVE_INTEGER_ERROR;
}
// Rest of the function.
}
void funUser(int in)
{
// Call fun4. Be prepared to deal with the exception or let it be
// dealt with another function higher up in the call stack.
// It makes sense to catch the exception only if this function do
// something useful with it.
fun4(in);
// Rest of the function.
}
Hope this gives you enough background to adopt an appropriate error handling strategy for your project.
I have a (Class member) function which I wish to avoid app crash due to ambiguity. For that purpose I have added a try catch bock as shown below:
void getGene(unsigned int position){
T val;
try {
val = _genome.at(_isCircular ? position % _genome.size() : position);
}
catch (std::exception& e) {
std::cerr << "Error in [" << __PRETTY_FUNCTION__ << "]: "
<< e.what() << std::endl;
exit(1);
}
return val;
}
Now, I wish to add a Boost unit test, which I thought of doing something like
BOOST_AUTO_TEST_CASE(nonCircularGenome_test){
// set size to 10
test.setSize(10);
// set non circular
test.setNonCircular();
// gene at site # 12 does not exist in a 10-site long genome, must throw an exception
BOOST_CHECK_THROW(test.getGene(12), std::out_of_range);
The problem is, I can't get both these things work. The try-catch block works well in release setup. However, this test works, only if I remove the try-catch block and let the function throw the exception.
What is the best way to get both these things working, so that a user is prompted with correct error on the go, while tests check explicitly on debug? One way is the use #ifdef/#endif DEBUG blocks, but I wish to avoid pre-processor macros.
Thanks in advance,
Nikhil
You seem to be misunderstanding the scope and purpose of exceptions - and perhaps of error handling in general.
First of all, you should define what are the pre-conditions of your function: does getGene() always expect position to be a valid one? Does it expect its clients to never provide invalid positions?
If that is the case, a client that provides an invalid position (even if the client is a test routine) is breaking the contract with the getGene() (in particular, it is breaking its pre-condition), and breaking a contract is undefined behavior by definition. You cannot test undefined behavior, so you should remove your test.
On the other hand, if your function has a wide contract, i.e. it allows the clients to pass in any position (even invalid ones) and (a) throws an exception or (b) returns an error code to communicate failure when the position is invalid, then the exit(1) line should not be there, because you are quitting the program and control is not transferred back to the caller.
One possibility is to re-throw the exception after logging the diagnostic:
T getGene(unsigned int position){
T val;
try {
val = _genome.at(_isCircular ? position % _genome.size() : position);
}
catch (std::exception& e) {
std::cerr << "Error in [" << __PRETTY_FUNCTION__ << "]: "
<< e.what() << std::endl;
throw;
// ^^^^^
}
return val;
}
And if you do not need to print a diagnostic, just let the exception naturally propagate:
T getGene(unsigned int position){
return _genome.at(_isCircular ? position % _genome.size() : position);
}
Is there a way to add or edit the message thrown by assert? I'd like to use something like
assert(a == b, "A must be equal to B");
Then, the compiler adds line, time and so on...
Is it possible?
A hack I've seen around is to use the && operator. Since a pointer "is true" if it's non-null, you can do the following without altering the condition:
assert(a == b && "A is not equal to B");
Since assert shows the condition that failed, it will display your message too. If it's not enough, you can write your own myAssert function or macro that will display whatever you want.
Another option is to reverse the operands and use the comma operator. You need extra parentheses so the comma isn't treated as a delimiter between the arguments:
assert(("A must be equal to B", a == b));
(this was copied from above comments, for better visibility)
Here's my version of assert macro, which accepts the message and prints everything out in a clear way:
#include <iostream>
#ifndef NDEBUG
# define M_Assert(Expr, Msg) \
__M_Assert(#Expr, Expr, __FILE__, __LINE__, Msg)
#else
# define M_Assert(Expr, Msg) ;
#endif
void __M_Assert(const char* expr_str, bool expr, const char* file, int line, const char* msg)
{
if (!expr)
{
std::cerr << "Assert failed:\t" << msg << "\n"
<< "Expected:\t" << expr_str << "\n"
<< "Source:\t\t" << file << ", line " << line << "\n";
abort();
}
}
Now, you can use this
M_Assert(ptr != nullptr, "MyFunction: requires non-null argument");
And in case of failure you will get a message like this:
Assert failed: MyFunction: requires non-null argument
Expected: ptr != nullptr
Source: C:\MyProject\src.cpp, line 22
Nice and clean, feel free to use it in your code =)
BOOST_ASSERT_MSG(expre, msg)
http://www.boost.org/doc/libs/1_51_0/libs/utility/assert.html
You could either use that directly or copy Boost's code. Also note Boost assert is header only, so you could just grab that single file if you didn't want to install all of Boost.
As zneak's answer convolutes the code somewhat, a better approach is to merely comment the string text you're talking about. ie.:
assert(a == b); // A must be equal to B
Since the reader of the assert error will look up the file and line anyway from the error message, they will see the full explanation here.
Because, at the end of the day, this:
assert(number_of_frames != 0); // Has frames to update
reads better than this:
assert(number_of_frames != 0 && "Has frames to update");
in terms of human parsing of code ie. readability. Also not a language hack.
assert is a macro/function combination. you can define your own macro/function, using __FILE__, __BASE_FILE__, __LINE__ etc, with your own function that takes a custom message
If the assert is done within a class, an alternative approach is to call a static predicate function with a self-describing name. If the assertion fails, the message will already contain the predicate's pretty and self-describing name.
E.g.:
static bool arguments_must_be_ordered(int a, int b) {return a <= b;}
void foo(int a, int b)
{
assert(arguments_must_be_ordered(a, b));
// ...
}
You may even want to make that predicate function public so that the class' user can verify the precondition themselves.
Even if assert is not disabled for release builds, the compiler will likely inline the predicate if it's fairly trivial.
The same approach can be used for complex if conditions needing a comment. Instead of a comment, just call a self-describing predicate function.
You could also just write your own custom assert function. A very simple example:
bool print_if_false(const bool assertion, const char* msg) {
if(!assertion) {
// endl to flush
std::cout << msg << std::endl;
}
return assertion;
}
int main()
{
int i = 0;
int j = 1;
assert(print_if_false(i == j, "i and j should be equal"));
return 0;
}
play with the code.
The assertion reads Assertion print_if_false(i == j, "i and j should be equal").
int x=10, y=25;
assert(x > y); // Add message along with this assert
Option 1) Since fprintf returns number of characters printed so we can or assert expression with !fprintf. Using stderr here since this is an error message
assert((x > y) || !fprintf(stderr, "Error: x %d is expected to be greater than y %d \n", x, y));
We can wrap this inside a macro for convinient use.
// Define macro over assert
#define assert_msg(cond, fmt, ...) assert( cond || !fprintf(stderr, fmt, ##__VA_ARGS__))
// Use above macro
assert_msg(x > y, "Error: x %d is expected to be greater than y %d \n", x, y);
Option 2) Define error message wrapped inside lambda.
auto err = [x, y] { fprintf(stderr, "Error: x %d should be greater than y %d \n", x, y); return false; };
assert((x > y) || err()); // Assert calls lambda func only when condition fails
Here is the dumped message.
Error: x 10 should be greater than y 25
File.cpp:10: int main(): Assertion `(x > y) || err()' failed.
Option 3) Or we can refine above solution to do it in one line with help of immediately invoked lambda
assert((x > y) || ([x, y] { fprintf(stderr, "Error: x %d is expected to be greater than y %d \n", x, y); return false; }()));
For vc, add following code in assert.h,
#define assert2(_Expression, _Msg) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Msg), _CRT_WIDE(__FILE__), __LINE__), 0) )
I have a function definition, where i call multiple functions. Even if one of the function fails i need to go ahead and call the rest of the functions and finally return a single error saying whether any of the function call failed. The approach which i had followed was
int function foo()
{
int res, res1, res2, res3;
res1 = function1();
res2 = function2();
res3 = function3();
if (res1 == -1 || res2 == -1 || res3 == -1)
{
res = -1;
}
return res;
}
The possible another approach is
int function foo()
{
int res;
if (function1() == -1)
{
res = -1;
}
if (function2() == -1)
{
res = -1;
}
if (function3() == -1)
{
res = -1;
}
return res;
}
Which is a better approach?
Thanks in advance.
No difference at all, both will be optimized to same machine code. Preference, maintainability, and that depends on team guidelines, preferences.
First priority, make the code correct. That's more important than readability and optimization.
That means you need to consider what the function should return in the case where the functions it calls all succeed.
Many of the answers given to this question change the result returned or might return a failure indication if the 'sub-functions' all succeed. you need to take care not to do this.
Personally, I think the overall form of your first option is pretty good - it makes clear that the 3 sub-functions are called regardless of whether one or more of them fail. The one problem is that it returns an indeterminate result if all those functions succeed.
Be wary of answers that use bitwise-or to combine results - there are at least 2 potential problems:
as John Marshall pointed out in several comments, the order of evaluation is indeterminate. This means that if you simply string the function calls with bitwise-or the functions may be called in any order. This might not be a problem if there are no ordering dependencies between the functions, but usually there are - especially if you don't care about the returned value except as a s success/fail indicator (if you aren't using the return value, then the only reason to call the function is for its side effects)
If the functions can return positive, non-zero values when they succeed, then testing for failure becomes a bit trickier than just checking if the results or'ed together are non-zero.
Given these two potential problems, I think there's little reason to try to do anything much fancier than option 1 (or your second option) - just make sure you set res to a success value (0?) for the situation where none of the sub-functions fail.
What about:
int foo ()
{
bool failed = false;
failed |= (function1() != 0);
failed |= (function2() != 0);
failed |= (function3() != 0);
return failed? -1 : 0;
}
You could also collapse the three calls into a single expression and omit the failed variable altogether (at the expense of readability):
int foo ()
{
return ((function1() != 0) | (function2() !=0 ) | (function3() != 0))? -1 : 0;
}
I like the first approach when function1 function2 and function3 have the same signature because I can put them in a function pointer table and loop over the entries, which makes adding function4 alot easier.
If you can define any precise convention about return values you can simply use bitwise or:
int foo() {
if (function1() | function2() | function3())
return -1;
else
return 0;
}
I like the second approach better. If you want one-liners, you can do something like...
char success = 1;
success &= (foo() == desired_result_1);
success &= (bar() == desired_result_2);
etc.
The 2nd is a "better" approach. However, I'd go more without the needless carrying around of an indicator variable:
if( function2() == -1 ){
return -1;
}
Suggestion: (no magic numbers)
I'd also not use "magic numbers" like you've used it. Instead:
if( check_fail( function2() ) ){
return FAILED;
}
more clearly illustrated what you're thinking. Intent is easier to maintain. Magic numbers can sometimes wind up hurting you. For instance, I've known financial guys who couldn't understand why a transaction costing "$-1.00" caused their application to behave abnormally.
In the first form you're not checking the status until all 3 calls are completed. I think this signals your intent the clearest. The second form more closely resembles the more usual case, where you return early if an error is detected.
It's a subtle thing either way. You shouldn't be asking us strangers on the internet, you should be asking the rest of your team, because they're the ones who will have to live with it.
You use bitwise operators to make a 'neat' variant that doesn't need temp variables and has other fancyness too(with the more advanced operators): return func1()|func2();(this is the same as using logical or, ||). However, if you require checking a specific function in the callee, you can create a bitset: return func1() << 1 | func2(); (this assumes that they return 1 or zero)
I'd vote for the second one as well.
This question reminded me of something similar I do in one of my projects for form validation.
I pass in a reference to an empty string. With each condition I want to check, I either add a line of text to the string, or I don't. If after every test the string is still empty, then there were no errors, and I continue processing the form. Otherwise, I print the string as a message box (which describes the problems), and ask the user to fix the errors.
In this case I don't really care what the errors are, just that there are errors. Oh, and as a bonus, my validation code documents itself a bit because the errors that the user sees are right there.
Use local variable if you need to reuse the result somewhere. Else, call and compare.
int foo() {
return function1() | function2() | function3();
}
Yet another option: pass a pointer to the status variable to each function and have the function set it only if there is an error.
void function1(int *res)
{
bool error_flag = false;
// do work
if (error_flag && (res != NULL)
{
*res = ERROR;
}
}
// similar for function2, function3, ...
int foo()
{
int res = OK;
function1(&res);
function2(&res);
function3(&res);
return res;
}
Since all 3 functions always have to get called first and only then you care about the result, I would go for the first solution, because the order of the statements reflects this. Seems more clear to me. Also, I generally don't like functions that do more than just return a value (i.e. that have side effects) in if-clauses, but that's a personal preference.
This sounds like a job for the abundant Perl idiom "<try something> || die()".
int foo() {
int retVal = 0;
function1() != -1 || retval = -1;
function2() != -1 || retval = -1;
function3() != -1 || retval = -1;
// ...
return retVal;
}
I write it this way:
int foo()
{
int iReturn = 0;
int res1 = function1();
if (res1 == -1)
{
return iReturn;
}
int res2 = function2();
if (res2 == -1)
{
return iReturn;
}
int res3 = function3();
if (res3 == -1)
{
return iReturn;
}
return res;
}
As a coding rule, you should declare your variables as close to the place where it is used.
It is good to use intermediate variable like your res1, res2, res3.
But choose a good name so as you intent is clear when you get the value from the function.
And be careful, in the example you've given us, you never assigned the int res; that may be returned when success. The coding rule is to initialize your variable as soon as you can.
So you should also initialize your res1 res2 res3 immidiatbly.
Returning an uninitialized value leads to undefined behaviour.
I've seen code like this before which might be a little cleaner:
bool result = true;
result = function1() == -1 && result;
result = function2() == -1 && result;
result = function3() == -1 && result;
return result?-1:0;
Edit: forgot about short circuiting.