ICS 65 Fall 2011
Acceptable Coding Practices


General style standards

Paying attention to coding style is important, for many reasons. Professional programmers need not only to be able to read and understand their own code, months or even years after originally writing it, but also to be able to read and understand code written by others, oftentimes in the absence of the original programmer. Programmers who write code in a clear style with adequate documentation benefit not only themselves, but all other members of their team, as well as future members of the team. There is nothing more frustrating as a programmer than inheriting responsibility for someone else's code, only to find that the code is designed poorly, written cryptically, and documented shabbily (or not at all!). Yet much of the code that I've inherited during my career has exhibited one or more of these shortcomings!

The C++ code that you write for this course should follow the style and documentation conventions described in the list below.


Use of language features (or, C++ is not C)

Moreso than many programming languages, C++ provides several different ways to accomplish many goals. This is due in large part to its retention of features from C, but also due to the complexity and orthogonality of the language. Still, some solutions are clearly better than others, and in some cases I'll be requiring you to use one over another. Some of you will have prior C++ experience, from a high school or community college course, from the industry, or a variety of other sources. It's important that you read this section, so that you understand that the way you're accustomed to doing things may not be acceptable in this course. (Rest assured, I'll not only explain what you're not allowed to do, but also why.)

As a basic rule of thumb, if there's an approach that is a holdover from C and another that is unique to C++, in almost all cases, the new approach was introduced into C++ to correct a deficiency in C, so the C++ approach should be preferred.

Some examples of the "off-limits" C features, and their better C++ alternatives, follow.

The preprocessor

Because of its C lineage, C++ requires its compilers to run the C preprocessor before compiling a file. The C preprocessor reads the contents of a file looking for preprocessor directives. For example, it's responsible for replacing an #include directive with the contents of the included file. It's also responsible for finding macros defined with the #define directive, then replacing all uses of the macro with the appropriate code defined in the macro, substituting macro parameters as needed.

The preprocessor is an outdated and error-prone tool that exists mostly to preserve C compatibility. Most of its uses have been replaced by better C++ equivalents. Therefore, you may not use any preprocessor directives, except in the following cases where it is unavoidable:

Other than the cases described above, you may not declare any other macros using #define. Most uses of macros have been replaced by better, safer C++ alternatives. (This is not to say that they don't still have their uses — such as enabling conditional compilation of code under different circumstances to support, for example, cross-platform C++ programming — or that you don't run into them in professional C++ programs, but we won't have any need for them in this course.) If you want to declare a named constant, declare a const variable of the appropriate type, which will make the use of the constant type-safe. If you want to declare a short macro function, write an inline function instead, which will allow type checking of parameters passed to it, while still (usually) providing the efficiency benefits of macros. If you want to give a type a new name, use a typedef, which is a clearer alternative that also helps the compiler to preserve type safety and provide better error messages.

Structs vs. classes

The reality in the C++ Standard is that a struct and a class are essentially the same. The only difference is their default behavior (e.g., whether any members declared before an access specifier are private or public). Since I'm not allowing you to rely on the default behavior (see the General Style Standards section above), the difference between them is moot. For that reason, I'm not allowing you to declare structs at all. Use classes instead.

It should be noted that structs do have their uses, even in properly written C++ code. They're typically used to aggregate public data of heterogenous types, in situations where member functions are not needed. While many such uses are better implemented as classes, sometimes a struct is what you want (particularly when you're communicating with a library written in C — which doesn't have any other way of aggregating heterogenous data — or directly interfacing with hardware).

The Standard C Library

You may not use Standard C Library functions where alternatives exist in the Standard C++ Library. The most obvious examples are functions like printf, scanf, fopen, and other I/O functions, or algorithm implementations like qsort that have been implemented much more cleanly in the Standard C++ Library. Other examples abound.

So, how do you know what functions are part of the Standard C Library? All of the Standard C++ Library functions (including the Standard C Library functions) are declared in headers whose names do not end in .h. For example, the iostream library is in a header that is simply called iostream. The Standard C Library has been pulled into the Standard C++ Library, but all of their headers have been renamed so that they begin with the letter c, such as cstdio (instead of stdio.h) and cstdlib (instead of stdlib.h).

The "old" Standard C++ Library

C++ has undergone multiple revisions in its lifetime. Much of the old library code, from the days before the language was completely standardized, still exists and is installed along with your compiler. For example, there is an older version of the iostream library available. These old libraries are distinguished by the names of their headers; they all end with .h. For example, there is a file called iostream.h that you can include; there's also an iostream file that you can include. Avoid the versions of standard headers that end in .h; they've been replaced by newer, better versions!

C-style strings

You may only use C-style strings (i.e., char* or char[]) where it is unavoidable. For example, when you call certain functions in the C++ iostream library, you'll need to pass a char* as a parameter. Still, remember that the string class contains a c_str( ) function that returns a C-style string. That means you can use strings exclusively, and temporarily convert them to char*'s only when absolutely necessary. For example, here's a function that begins by opening a file for reading:

    void openThatFile(const string& filename)
    {
        ifstream fin(filename.c_str(), ios::in);
    
        // ...
    }

Notice how I've declared the function to take a string parameter. This is by design. As far as I'm concerned, C-style strings are a low-level implementation detail, so I'd like to bury them in my code as deeply as possible. Designing my function this way allows the rest of my program to happily use nothing but strings, and be blissfully unaware that an ugly concept such as char* even exists.

Some of you with prior C experience will want to use functions like strtok and sscanf to parse strings. Don't do it! Instead, you should use an istringstream (which is better, anyway, because it is derived from istream and, hence, the same code can be used to parse input from a string, a file, or the console).

Casting

C (and, thus, C++) provides a deceptively simple syntax for casting values from one type to another. The syntax strongly resembles the Java syntax:

    // a simple C-style cast, which is legal (but antiquated) C++
    double d = 3.5;
    int i = (int) d;

However, there are two problems with the C-style casting syntax. Firstly, it has a tendency to blend into your code, because parentheses are used for so many different purposes in C++. The low-key syntax belies the danger inherent whenever you use a cast. Secondly, and more importantly, casting is done for many different reasons; a C-style cast says nothing about the reason for casting. For example, you might be casting a floating-point number to an integral one, as above, which has a reasonably good chance of being "successful" (i.e., it stands a good chance of behaving as you'd like it to). Or you might be casting between pointer types, such as a cast downward through inheritance hierarchy:

    // assume B is derived from A
    void foo(A* a)
    {
        B* b = (B*) a;
        // ...
    }

Or you might be removing the const qualifier from a variable (which, by the way, is almost always a bad idea!):

    void foo(const MyObject* a)
    {
        MyObject* b = (MyObject*) a;
        b->someNonConstFunction();
    }

Each of the casts above entail different risks and offer different benefits. This is the reason behind the new C++ casting syntax. In C++, casts tell you not only the destination type, but also provide insight about why the cast is being performed and what risks are involved. The examples above could be rewritten as follows:

    // This isn't a cast, per se, but accomplishes the same goal.
    // This syntax should be used for direct type conversions.
    double d = 3.5;
    int i = int(d);

    // static_cast should be used whenever you're performing a downcast between
    // pointer types.  If you'd like to ensure that the cast is checked at
    // run-time, you should use a dynamic_cast instead.  If your cast is even
    // more unsafe (i.e., between two essentially unrelated types), such as
    // casting to and from a void*, use reinterpret_cast.
    void foo(A* a)
    {
        B* b = static_cast<B*>(a);
        // ...
    }

    // const_cast should be used whenever you're removing the const
    // qualifier from the type of a variable, but are not otherwise
    // changing its type.
    void foo(const MyObject* a)
    {
        // This is rarely a good idea; very scary business!
        MyObject* b = const_cast<MyObject*>(a);

        b->someNonConstFunction();
    }

You may not use C-style casts in any of the code you write in this course. Instead, you must use the C++-style casts and/or type conversions. Furthermore, you should think quite a bit before you use any casts at all; many of the more recently-added C++ features (e.g., templates, covariant return types) have almost entirely obviated casting. If you're using lots of casts, you should probably rethink your design.