style and techniques

Save to:
Instapaper Pocket Readability

Miscellaneous Style Issues

Is int* p; right or is int *p; right?

Both are “right” in the sense that both are valid C and C++ and both have exactly the same meaning. As far as the language definitions and the compilers are concerned we could just as well say int*p; or int * p;.

The choice between int* p; and int *p; is not about right and wrong, but about style and emphasis. C emphasized expressions; declarations were often considered little more than a necessary evil. C++, on the other hand, has a heavy emphasis on types.

A “typical C programmer” writes int *p; and explains it as “*p is what is the int” emphasizing syntax, and may point to the C (and C++) declaration grammar to argue for the correctness of the style. Indeed, the * binds to the name p in the grammar.

A “typical C++ programmer” writes int* p; and explains it as “p is a pointer to an int:” emphasizing type. Indeed the type of p is int*. A C++ mindset prefers that emphasis and sees it as important for using the more advanced parts of C++ well.

The critical confusion comes (only) when people try to declare several pointers with a single declaration:

    int* p, p1; // probable error: p1 is not an int*

Placing the * closer to the name does not make this kind of error significantly less likely.

    int *p, p1; // probable error?

Declaring one name per declaration minimizes the problem – in particular when we initialize the variables. People are far less likely to write:

    int* p = &i;
    int p1 = p; // error: int initialized by int*

And if they do, the compiler will complain.

Whenever something can be done in two ways, someone will be confused. Whenever something is a matter of taste, discussions can drag on forever. Stick to one pointer per declaration and always initialize variables and the source of confusion disappears. See The Design and Evolution of C++ for a longer discussion of the C declaration syntax.

Which layout style is the best for my code?

Such style issues are a matter of personal taste. Often, opinions about code layout are strongly held, but probably consistency matters more than any particular style. Like most people, you’ll have a hard time constructing a solid logical argument for your preferences.

Design issues, such as the use of abstract classes for major interfaces, use of templates to present flexible type-safe abstractions, and proper use of exceptions to represent errors, are far more important than the choice of layout style.

How do you name variables? Do you recommend Hungarian notation?

There are two kinds of “Hungarian notation” – “Systems Hungarian,” the kind that encodes the type in the variable name, is widely regarded as an antipattern. This FAQ is about that kind of Hungarian.

Encoding a type in a variable name is a technique that can be useful in untyped languages, but is completely unsuitable for a language that supports generic programming and object-oriented programming – both of which emphasize selection of operations based on the type an arguments (known to the language or to the run-time support). In this case, “building the type of an object into names” simply complicates and minimizes abstraction. To various extent, you will have similar problems with every scheme that embeds information about language-technical details (e.g., scope, storage class, syntactic category) into names. Yes, in some cases, building type hints into variable names can be helpful, but in general, and especially as software evolves, this becomes a maintenance hazard and a serious detriment to good code. Avoid it like the plague.

If after all that you still want to risk encoding a type in a variable name anyway, be our guest: If you do decide to play Hungarian Games, may the odds be ever in your favor.

Moving on…

So, if you shouldn’t like naming a variable after its type, what should you like and recommend? Name a variable (function, type, whatever) based on what it is or does. Choose meaningful name; that is, choose names that will help people understand your program. Even you will have problems understanding what your program is supposed to do if you litter it with variables with easy-to-type names like x1, x2, s3, and p7. Abbreviations and acronyms can confuse people, so use them sparingly. Acronyms should be used sparingly. Consider, mtbf, TLA, myw, RTFM, and NBV. They are obvious, but wait a few months and you will have forgotten at least one.

Short names, such as x and i, are meaningful when used conventionally; that is, x should be a local variable or a parameter and i should be a loop index. Ain’t nothing wrong with that.

Don’t use overly long names; they are hard to type, make lines so long that they don’t fit on a screen, and are hard to read quickly. These are probably okay:

    partial_sum    element_count    stable_partition

These are probably too long:

    the_number_of_elements    remaining_free_slots_in_symbol_table

The ISO standard prefers to use underscores to separate words in an identifier (e.g, element_count) rather than alternatives, such as elementCount and ElementCount. Never use names with all capital letter (e.g., BEGIN_TRANSACTION) because that’s conventionally reserved for macros. Even if you don’t use macros, someone might have littered your header files with them. One common practice is to use an initial capital letter for types (e.g., Square and Graph). The C++ language and standard library don’t use capital letters, so it’s int rather than Int and string rather than String. That way, you can recognize the standard types.

Avoid names that are easy to mistype, misread, or confuse. For example

    name    names    nameS
    foo     f00
    fl      f1       fI       fi

The characters 0, o, O, 1, l, and I are particularly prone to cause trouble.

Often, your choice of naming conventions is limited by local style rules. Remember that a maintaining a consistent style is often more important than doing every little detail in the way you think best.

Should I put const before or after the type?

Many authors put it before, but that’s a matter of taste. const T and T const were – and are – (both) allowed and equivalent. For example:

    const int a = 1;    // ok
    int const b = 2;    // also ok

Using the first version will likely confuse fewer programmers (“is more idiomatic”).

Note that in const pointers, const always comes after the *. For example:

    int *const p1 = q;  // constant pointer to int variable
    int const* p2 = q;  // pointer to constant int
    const int* p3 = q;  // pointer to constant int

What good is static_cast?

Casts are generally best avoided. With the exception of dynamic_cast, their use implies the possibility of a type error or the truncation of a numeric value. Even an innocent-looking cast can become a serious problem if, during development or maintenance, one of the types involved is changed. For example, what does this mean:

    x = (T)y;

We don’t know. It depends on the type T and the types of x and y. T could be the name of a class, a type alias, or maybe a template parameter. Maybe x and y are scalar variables and (T) represents a value conversion. Maybe x is of a class derived from y’s class and (T) is a downcast. Maybe x and y are unrelated pointer types. Because the C-style cast (T) can be used to express many logically different operations, the compiler has only the barest chance to catch misuses. For the same reason, a programmer may not know exactly what a cast does. This is sometimes considered an advantage by novice programmers and is a source of subtle errors when the novice guessed wrong.

The “new-style casts” were introduced to give programmers a chance to state their intentions more clearly and for the compiler to catch more errors. For example:

    int a = 7;
    double* p1 = (double*) &a;          // ok (but a is not a double)
    double* p2 = static_cast<double*>(&a);  // error
    double* p2 = reinterpret_cast<double*>(&a); // ok: I really mean it

    const int c = 7;
    int* q1 = &c;           // error
    int* q2 = (int*)&c;     // ok (but *q2=2; is still invalid code and may fail)
    int* q3 = static_cast<int*>(&c);    // error: static_cast doesn't cast away const
    int* q4 = const_cast<int*>(&c); // I really mean it

The idea is that conversions allowed by static_cast are somewhat less likely to lead to errors than those that require reinterpret_cast. In principle, it is possible to use the result of a static_cast without casting it back to its original type, whereas you should always cast the result of a reinterpret_cast back to its original type before using it to ensure portability.

A secondary reason for introducing the new-style cast was that C-style casts are very hard to spot in a program. For example, you can’t conveniently search for casts using an ordinary editor or word processor. This near-invisibility of C-style casts is especially unfortunate because they are so potentially damaging. An ugly operation should have an ugly syntactic form. That observation was part of the reason for choosing the syntax for the new-style casts. A further reason was for the new-style casts to match the template notation, so that programmers can write their own casts, especially run-time checked casts.

Maybe, because static_cast is so ugly and so relatively hard to type, you’re more likely to think twice before using one? That would be good, because casts really are mostly avoidable in modern C++.

So, what’s wrong with using macros?

Macros do not obey the C++ scope and type rules. This is often the cause of subtle and not-so-subtle problems. Consequently, C++ provides alternatives that fit better with the rest of C++, such as inline functions, templates, and namespaces.

Consider:

    #include "someheader.h"

    struct S {
        int alpha;
        int beta;
    };

If someone (unwisely) has written a macro called alpha or a macro called beta this may not compile or (worse) compile into something unexpected. For example, someheader.h may contain:

    #define alpha 'a'
    #define beta b[2]

Conventions such as having macros (and only macros) in ALLCAPS helps, but there is no language-level protection against macros. For example, the fact that the member names were in the scope of the struct didn’t help: Macros operate on a program as a stream of characters before the compiler proper sees it. This, incidentally, is a major reason for C and C++ program development environments and tools have been unsophisticated: the human and the compiler see different things.

Unfortunately, you cannot assume that other programmers consistently avoid what you consider “really stupid”. For example, programmers have reported sightings of macros containing a goto along with heard arguments that might – in a weak moment – appear to make sense. For example:

    #define prefix get_ready(); int ret__
    #define Return(i) ret__=i; do_something(); goto exit
    #define suffix exit: cleanup(); return ret__

    int f()
    {
        prefix;
        // ...
        Return(10);
        // ...
        Return(x++);
        //...
        suffix;
    }

Imagine being presented with that as a maintenance programmer; “hiding” the macros in a header – as is not uncommon – makes this kind of “magic” harder to spot.

One of the most common subtle problems is that a function-style macro doesn’t obey the rules of function argument passing. For example:

    #define square(x) (x*x)

    void f(double d, int i)
    {
        square(d);  // fine
        square(i++);    // ouch: means (i++*i++)
        square(d+1);    // ouch: means (d+1*d+1); that is, (d+d+1)
        // ...
    }

The “d+1” problem is solved by adding parentheses in the “call” or in the macro definition:

    #define square(x) ((x)*(x)) /* better */

However, the problem with the (presumably unintended) double evaluation of i++ remains.

And yes, it is well known that there are things known as macros that doesn’t suffer the problems of C/C++ preprocessor macros. However, the C++ community generally has no ambitions for improving C++ macros. Instead, we recommend the use of facilities from the C++ language proper, such as inline functions, templates, constructors (for initialization), destructors (for cleanup), exceptions (for exiting contexts), etc.

How do you pronounce cout?

Many people, including Stroustrup, pronounce cout as “see-out”. The “c” stands for “character” because iostreams map values to and from byte (char) representations.

Lots of people pronounce it as rhyming with “gout” and “spout.”

How do you pronounce char?

Many people pronounce char the same as the English verb “char” (as in, to char wood in a fire). Others pronounce it like the English word “care,” the same as the first syllable of “character.”

But pronounce it however you like, we don’t char.