Using "return" to end a function in C++

MrAl

Joined Jun 17, 2014
13,704
This is a good question because it comes up often even in very experienced teams. I've had code rejected in the past because I violated the "only one exit point" "rule". This is a style question and I have never supported this one exit point rule myself because it's not meaningful.

A function often has several exit reasons and making the developer mask this with one exit point is not conveying clarity.

I think its much better to clearly indicate each exit scenario with an immediate return, with the one exit point rule your code might reach a point where its just done, over, yet someone reading it likely won't know because the code is forced to artificially plod along until the end.
Hi,

The reason for the one exit point rule is so that in more complicated code there is no mistake about how the program flows. With multiple exit points, you have to be very sure your conditionals all have the right exit point and sometimes that is harder to do then you might think.
With one exit point the programmer is forced to think out the code so that it always falls through to the end and has an exit value structure that is always the same.
Also, program maintenance is simpler because the reviewer does not have to figure out why each point is exiting, there's only one point that exits. It's almost like an electrical circuit if you have more than one output it's going to be more complicated than with just one output.

We could look at some code examples i guess to see the points to all this. One problem that comes to mind is when the code section exits in various places and it looks ok to add something that also exits early without noticing that there was an open file that never got closed before this new exit point.

I am sure you also know full well about the "goto" debacle. It can lead to very hard to read code sections. Structured programming is always the goal so that's the bottom rule really. The structure should be quickly understood.
 

nsaspook

Joined Aug 27, 2009
16,325
This is a good question because it comes up often even in very experienced teams. I've had code rejected in the past because I violated the "only one exit point" "rule". This is a style question and I have never supported this one exit point rule myself because it's not meaningful.

A function often has several exit reasons and making the developer mask this with one exit point is not conveying clarity.

I think its much better to clearly indicate each exit scenario with an immediate return, with the one exit point rule your code might reach a point where its just done, over, yet someone reading it likely won't know because the code is forced to artificially plod along until the end.
Completely agree when the code is not part of a multilevel resource allocation exit failure that must be rolled back (in C this is a good time to consider using GOTO) in order. If you can exit a function early because some function limits test failed, Do It. Multiple success return points should been seen as excessive function complexity.
 
Last edited:

djsfantasi

Joined Apr 11, 2010
9,237
A recurring weakness even in modern imperative languages, is the tolerance of ignoring returned values, C, C++, Java, C# and so on, all allow this yet it's the seed of some bad practices and the compilers should at least issue a warning.

In functional languages this is forbidden, impossible.
Return codes are there for a reason. I’ve seen a lot of code where the programmer can’t find why something is broken, where the break would be obvious if return codes were logged. Even if only during debug. Conditional compiles can easily include such code only when in debug mode.
 

ApacheKid

Joined Jan 12, 2015
1,762
Hi,

The reason for the one exit point rule is so that in more complicated code there is no mistake about how the program flows. With multiple exit points, you have to be very sure your conditionals all have the right exit point and sometimes that is harder to do then you might think.
With one exit point the programmer is forced to think out the code so that it always falls through to the end and has an exit value structure that is always the same.
Also, program maintenance is simpler because the reviewer does not have to figure out why each point is exiting, there's only one point that exits. It's almost like an electrical circuit if you have more than one output it's going to be more complicated than with just one output.

We could look at some code examples i guess to see the points to all this. One problem that comes to mind is when the code section exits in various places and it looks ok to add something that also exits early without noticing that there was an open file that never got closed before this new exit point.

I am sure you also know full well about the "goto" debacle. It can lead to very hard to read code sections. Structured programming is always the goal so that's the bottom rule really. The structure should be quickly understood.
Yes, I've heard several cases made over the years and there are some good reasons that some developers find the rule helpful but I'm not convinced that it truly improves quality or maintainability.

For example a common (and very good) practice when developing APIs is strict argument validation, if we have a function that takes say four pointers then we can write this in two ways:



Code:
void InsertOrCreateEntry(void * p0, void * p1, void * p2, void * p3, int * status)
{
  
    if (p0 != NULL)
        if (p1 != NULL)
            if (p2 != NULL)
                if (p3 != NULL)
                {
                    // all good - do stuff
                    *status = 0;
                }
                else
                {
                    *status = 4;
                }
            else
            {
                *status = 3;
            }
        else
        {
          
            *status = 2;
        }
    else
    {
        *status = 1;
    }
}
or

Code:
void InsertOrCreateEntry(void * p0, void * p1, void * p2, void * p3, int * status)
{
    if (p0 == NULL)
    {
        *status = 1;
        return;
    }
  
    if (p1 == NULL)
    {
        *status = 2;
        return;
    }
  
    if (p2 == NULL)
    {
        *status = 3;
        return;
    }

    if (p3 == NULL)
    {
        *status = 4;
        return;
    }
  
    // all good - do stuff

    *status = 0;
}
right away you can see what I regard as a huge drawback, the increasing degree of nested else clauses. In some code I've worked on (like at the Financial Times once, data feed processing) I worked on a large codebase that was rather old and had had this rule enforced throughout its lifetime.

The code was almost unfathomable, far more deeply nested than my example above. It was very hard to reason about it because it was never clear at any point in the logic if we were doing "good" processing or were on some branch of "bad" or "done" processing.

With the second approach we can be confident that if we're on line X then we still have processing to do because if we didn't we'd have left at some line < X.

Yes we can alleviate this to a large degree by allowing goto and then always jumping to some end steps with a goto, I'm not strictly against that either so long as it's well defined and done consistently, but once even that limited use of goto is allowed it can begin to proliferate unless strictly reviewed and enforced.
 

MrAl

Joined Jun 17, 2014
13,704
Hi,

Yes well maybe the golden rule is use whatever form will be most clear for the given application.
 

WBahn

Joined Mar 31, 2012
32,840
A recurring weakness even in modern imperative languages, is the tolerance of ignoring returned values, C, C++, Java, C# and so on, all allow this yet it's the seed of some bad practices and the compilers should at least issue a warning.

In functional languages this is forbidden, impossible.
The problem with even issuing warnings is that there are many functions that are used almost exclusively for their side effects (something that doesn't exist in a purely functional language) but that have return values that are seldom used but that can be useful in certain places. A classic example is the printf() family of functions in C. Few people ever use the return value (which is the number of characters printed) but this value can be quite useful when formatting tabular output.

If you require the compiler to issue a warning every time someone ignored the return value of printf(), all you will accomplish is one of two things -- training people to ignore compiler warnings (something that is already far too common) or forcing people to add meaningless code whose sole purpose is to prevent the warning. Seems like that would be accomplishing the same end result as requiring a single exit point does and for a far less laudible goal.
 

ApacheKid

Joined Jan 12, 2015
1,762
The problem with even issuing warnings is that there are many functions that are used almost exclusively for their side effects (something that doesn't exist in a purely functional language) but that have return values that are seldom used but that can be useful in certain places. A classic example is the printf() family of functions in C. Few people ever use the return value (which is the number of characters printed) but this value can be quite useful when formatting tabular output.

If you require the compiler to issue a warning every time someone ignored the return value of printf(), all you will accomplish is one of two things -- training people to ignore compiler warnings (something that is already far too common) or forcing people to add meaningless code whose sole purpose is to prevent the warning. Seems like that would be accomplishing the same end result as requiring a single exit point does and for a far less laudible goal.
I agree, I am not advocating we do the warning retrospectively, if to be done at all it should have been done from the outset, for example C# could have done this but tolerates it to this day too (but with these .Net languages it's not hard to write one's own "analyzers" for these kinds of specialist diagnostics).

As for printf() and friends, yes these can be useful sometimes but could be implemented as an in/out argument (i.e. a short * or something), this is all academic as you know but I've never loved this ability to simply disregard a returned value.
 
Top