What does style look like, part 7

Over the course of this series on style, I've touched on a lot of different aspects, today I want to discuss aspects C and C++ style specifically.

One of the things about computer languages in general is that there are often a huge number of options available to programmers to perform a particular task.

And whenever there's a choice to be made while writing programs, style enters into the picture.  As does religion - whenever the language allows for ambiguity, people tend to get pretty religious about their personal preferences.

For a trivial example, consider the act of incrementing a variable.  C provides three different forms that can be used to increment a variable. 

There's:

  • i++,
  • ++i,
  • i+=1, and
  • i=i+1.

These are all semantically identical, the code generated by the compiler should be the same, regardless of which you chose as a developer (this wasn't always the case, btw - the reason that i++ exists as a language construct in the first place is that the original C compiler wasn't smart enough to take advantage of the PDP-8's built-in increment instruction, and i++ allowed a programmer to write code that used it).

The very first time I posted a code sample, I used my personal style, of i+=1 and got howls of agony from my readers.  They wanted to know why on EARTH I would use such a stupid construct when i++ would suffice.  Well, it's a style issue :)

There are literally hundreds of these language specific style issues.  For instance, the syntax of an if statement (or a for statement) is:

if (conditional) statement

where statement could be either a single line statement or a compound statement.  This means that it's totally legal to write:

if (i < 10)     i = 0;

And it's totally legal to write

if (i < 10) {    i = 0;}

The statements are utterly identical from a semantic point of view.  Which of the two forms you choose is a style issue.  Now, in this case, there IS a fairly strong technical reason to choose the second form over the first - by putting the braces in always, you reduce the likelihood that a future maintainer of the code will screw up and add a second line to the statement.  It also spaces out the code (which is a good thing IMHO :) (there's that personal style coming back in again)).

Other aspects of coding that ultimately devolve to style choices are:

if (i == 10)

vs

if (10 == i)

In this case, the second form is often used to prevent the assignment within an if statement problem - it's very easy to write:

if (i = 10)

which is unlikely to be what the developer intended.  Again, this is a style issue - by putting the constant on the left of the expression, you cause the compiler to generate an error when you make this programming error.  Of course, the compiler has a warning, C4706, to catch exactly this situation, so...

Another common stylistic convention that's often found is:

do {    < some stuff >} while (false);

This one exists to allow the programmer to avoid using the dreaded "goto" statement.  By putting "some stuff" inside the while loop, it enables the use of the break statement to exit the "loop". Personally, I find this rather unpleasant, a loop should be a control construct, not syntactic sugar to avoid language constructs.

Speaking of goto...

This is another language construct that people either love or hate.  In many ways, Edsger was totally right about goto - it is entirely possible to utterly misuse goto. On the other hand, goto can be a boon for improving code clarity.  

Consider the following code:

HRESULT MyFunction(){    HRESULT hr;    hr = myCOMObject->Method1();    if (hr == S_OK)    {        hr = myCOMObject->Method2();        if (hr == S_OK)        {            hr = myCOMObject->Method3();            if (hr == S_OK)            {                hr = myCOMObject->Method4();            }            else            {                hr = myCOMObject->Method5();            }        }    }    return hr;}

In this really trivial example, it's vaguely clear what's going on, but it suffices.  One common change is to move the check for hr outside and repeatedly check it for each of the statements, something like:

    hr = myCOMObject->Method1();    if (hr == S_OK)    {        hr = myCOMObject->Method2();    }    if (hr == S_OK) 

What happens when you try that alternative implementation?

HRESULT MyFunction(){    HRESULT hr;    hr = myCOMObject->Method1();    if (hr == S_OK)    {        hr = myCOMObject->Method2();    }    if (hr == S_OK)    {        hr = myCOMObject->Method3();        if (hr == S_OK)        {            hr = myCOMObject->Method4();        }        else        {            hr = myCOMObject->Method5();        }    }    return hr;}

Hmm.  That's not as nice - some of it's been cleaned up, but the Method4/Method5 check still requires that you indent an extra level.

Now consider what happens if you can use gotos:

HRESULT MyFunction(){    HRESULT hr;    hr = myCOMObject->Method1();    if (hr != S_OK)    {        goto Error;    }    hr = myCOMObject->Method2();    if (hr != S_OK)    {        goto Error;    }    hr = myCOMObject->Method3();    if (hr == S_OK)    {        hr = myCOMObject->Method4();    }    else    {        hr = myCOMObject->Method5();    }    if (hr != S_OK)    {        goto Error;    }Cleanup:    return hr;Error:    goto Cleanup;}

If you don't like seeing all those gotos, you can use a macro to hide them:

#define IF_FAILED_JUMP(hr, tag) if ((hr) != S_OK) goto tagHRESULT MyFunction(){    HRESULT hr;    hr = myCOMObject->Method1();    IF_FAILED_JUMP(hr, Error);    hr = myCOMObject->Method2();    IF_FAILED_JUMP(hr, Error);    hr = myCOMObject->Method3();    if (hr == S_OK)    {        hr = myCOMObject->Method4();        IF_FAILED_JUMP(hr, Error);    }    else    {        hr = myCOMObject->Method5();        IF_FAILED_JUMP(hr, Error);    }Cleanup:    return hr;Error:    goto Cleanup;}

Again, there are no right answers or wrong answers, just choices.

Tomorrow, wrapping it all up.