EECS 311: DATA STRUCTURES

C++ Coding Standard

Acknowledgement

This page was initially generated by Sven Rosvall's Coding Standard Generator, and then edited substantially to clean up the somewhat ancient HTML, and add some additional points.

You should also look at the miscellaneous EECS 311 C++ tips, focussing on items particularly useful in various assignments.

For a list of C and C++ style guide's, check out Chris Lott's site. One of the nicer ones is the Geotechnical Software Services style guide. Most of the rules there are included here.

Don't be mislead by the term "style." These guidelines are not secondary matters of fashion or personal taste. They are the primary elements for developing code that is maintainable and efficient. For example, there is little or no disagreement between the guidelines below and Pete Isensee's C++ Optimization Strategies and Techniques.

Capitalization

Naming

Indentation and Spacing

Comments

Libraries

Files

Declarations

Statements

Expressions

Other Issues


Naming

Use sensible, descriptive names.

Do not use short cryptic names or names based on internal jokes. It should be easy to type a name without looking up how it is spelled.

Consider Roedy Green's essay on how not to name things as required reading for all programmers.

Exception: Loop variables and variables with a small scope (less than 20 lines) may have short names to save space if the purpose of that variable is obvious.

Only use English names.

It is confusing when mixing languages for names. English is the preferred language because of its spread in the software market and because most libraries used already use English.

Variables with a large scope should have long names, variables with a small scope can have short names.

Scratch variables used for temporary storage or indices are best kept short. A programmer reading such variables should be able to assume that its value is not used outside a few lines of code. Common scratch variables for integers are i, j, k, m, n and for characters c and d.

Use namespaces for identifiers declared in different modules

This avoids name clashes.

Use name prefixes for identifiers declared in different modules

This avoids name clashes.

Indentation and Spacing

Braces should follow "K&R Bracing Style".

The K&R Bracing Style was first introduced in The C Programming Language by Brian Kernighan and Dennis Ritchie.

The opening brace is placed at the end of the enclosing statement and the closing brace is on a line on its own lined up with the enclosing statement. Statements and declaration between the braces are indented relative to the enclosing statement.

Note that the opening brace of a function body is placed on a line on its own lined up with the function declaration.

Example:

void f(int a)
{
    int i;
    if (a > 0) {
        i = a;
    } 
    else {
        i = a;
    }
}

Code nested within braces should be indented consistently.

Common indentation amounts are 2 or 4 columns. Use one amount consistently.

Always enclose loop and conditional sub-statements in braces.

The code looks more consistent if all conditional and loop statements have braces. Even if there is only a single statement after the condition or loop statement today, there might be a need for more code in the future.

This also addresses a common concern that K&R, style makes it hard to see if a long if or for has an opening brace. There should always an opening brace, and no need to look for it.

Braces without any contents may be placed on the same line.

The only time when two braces can occur on the same line is when they do not contain any code.

while (...)
{}

Each statement should be placed on a line on its own.

There is no need to make code compact. Putting several statements on the same line only makes the code cryptic to read.

Declare each variable in a separate declaration.

This makes it easier to see all variables. It also avoids the problem of knowing which variables are pointers.

int* p, i;

It is easy to forget that the star belongs to the declared name, not the type, and look at this and say that the type is "pointer to int" and both p and i are declared to this type.

Lines should not exceed 78 characters.

Even if your editor handles long lines, other people may have set up their editors differently. Long lines in the code may also cause problems for other programs and printers.

Do not use tabs.

Tabs make the source code difficult to read where different programs treat the tabs differently. The same code can look very differently in different editors and different machines.

Avoid using tabs in your source code to avoid this problem. Use spaces instead.

Comments

Use C++ style comments.

Be consistent and use the // ... style comments.

All comments should be placed above the line the comment describes, indented identically.

Being consistent on placement of comments removes any question on what the comment refers to.

Libraries

Use the C++ standard containers where possible.

Use a vector not an array, use a string not a const char *. These containers are more flexible and less error prone.

Use iostreams, not C's printf and scanf.

Write cout << "n = " << n << endl; not printf("%d\n", n) because the iostream library are more powerful and can be extended to handle any data strucure, including ones you define.

Use the C++ versions of the C libraries.

For example, #include <cmath> not #include <math.h>, because the C++ versions work correctly with namespaces.

Use the C++ <algorithm> library where possible.

Don't write a for or while if it duplicates what a templated function in <algorithm> already does, e.g., copy or find. The algorithm version is usually shorter, definitely more communicative, and sometimes more efficient than what you would write.

Files

There should only be one externally visible class defined in each header file.

Having as few declarations as possible in a header file reduces header dependencies.

The header file should have the same name as the class plus extension hpp.

External non-member functions that belong to the class interface may also be declared in the same header file.

File name should be treated as case sensitive.

On Windows machines, using different case in a file name than what it actually has won't matter, but your code won't work if compiled and run on another platform, such as Unix.

C source files should have extension ".c".

C header files should have extension ".h".

C++ source files should have extension ".cpp".

C++ header files should have extension ".hpp".

Never #include .cpp code files.

Code files, i.e., .cpp and .c files, should never be #include'd. It causes the included code to be recompiled, even when unchanged, and can lead to multiple definition errors.

Instead, code files should be made part of the project file (makefile), compiled separately as needed, and linked in.

If you have code that doesn't compile correctly unless you include a code file, it means your header file is missing something, such as a forward declaration or a templated member function.

Inline functions should be declared in header files and defined in inline definition files.

The keyword inline should be used in both places. Using a separate inline file is useful to keep the header files clean and small. The separation is also useful where the inlining is disabled in debug builds. The inline file is then included from the source file instead of the header file to reduce compile time.

Header files must have include guards.

The include guard protects against the header file being included multiple times. Example:

#ifndef FILE_H
#define FILE_H
...
#endif

The name of the macro used in the include guard should have the same name as the file (excluding the extension) followed by the suffix "_H".

Header files should be self-contained

When a header is included, there should not be any need to include any other headers first. A simple way to make sure that a header file does not have any dependencies is to include it first in its corresponding implementation file. Example:

// implementation file for foobar.h 

#include "foobar.h"
#include <stdio.h>

...

Include system headers with <header-name> and project headers with "header-name.h".

There is no requirement that a C++ compiler keep system headers in .h files, only that #include <header-name> will cause the right names to be defined.

Put #include directives at the top of files.

Having all #include directives in one place makes it easy to find them.

Do not use absolute directory names in #include directives.

The directory structure may be different on other systems.

Declarations

Provide names of parameters in function declarations.

Parameter names document what the parameter is used for. The parameter names should be the same in all declarations and definitions of the function.

Use a typedef to define specializations of containers.

Instead of writing something like vector<string> over and over again on variable and parameter declarations, do typedef vector<string> WordList; and use WordList instead. The code will be much easier to read, and it will trivial to change to another container type when necessary.

Use a typedef to define a pointer to a function.

Pointers to functions have a strange syntax. The code becomes much clearer if you use a typedef for the pointer to function type. This typedef name can then be used to declare variables etc.

double sin(double arg);
typedef double (*TrigFunc)(double arg);

/* Usage examples */
TrigFunc myFunc = sin;
void callFunc(TrigFunc callback);
TrigFunc funcTable[10];

Prefer reference parameters over pointer parameters.

The syntax for using reference parameters is much cleaner than for using pointer parameters. No dereferencing is required.

Use constant reference parameters to pass a data structure to a function that does not modify the data structure.

Use reference parameters to pass a data structure to a function that does modify the data structure.

Use pointer parameters with dynamically allocated data structures.

Do not use exception specifications.

Exception specifications in C++ are not as useful as they look. The compiler does not make the code more efficient. On the contrary, the compiler has to insert code to check that called functions do not violate the specified exception specification at runtime.

Declare inherited functions virtual.

An inherited function is implicitly virtual if it is declared virtual in the base class. Repeat the virtual keyword when declaring an inherited function in a derived class to make it clear that this function is virtual.

Do not use global variables.

Global variables are initialised when the program starts whether it will be used or not. If global variables are using other global variables for their initialisation, there may be a problem if the dependent variables are not initialised yet. The initialisation order of global variables in different object files is not defined.

Use singleton objects instead, if you can't avoid it. A singleton object is only initialised when the object is used the first time. Singleton objects do not have the ordering problem as the dependent object will be initialised when it is used. However, watch out for cyclic dependencies in singleton object initialisations.

Do not use global variables or singleton objects.

Global variables and singleton objects hide what functions do, because side effects on globals and singleton objects are invisible in the declaration of the functions. To make it clear what inputs and outputs a function has, pass these objects as parameters to the functions.

Also, if the program may become multithreaded, it may be difficult to re-write the program to remove the global variables and singleton objects that cannot be shared between the threads.

Do not use global using declarations or directives in headers.

Bringing in names from a namespace to the global namespace may cause conflicts with other headers. The author of a header does not know in which context the header is used and should avoid polluting the global namespace. Instead, only use using declarations in the source files.

Use initializer lists rather than assignments in constructors.

Instead of this:

class Employee {
public:
  Employee(string name, string address)
  {
    myName = name;
    myAddress = address;
  }
  ...
private:
  string myName, myAddress;
};

write this:

class Employee {
public:
  Employee(string name, string address)
    : myName(name), myAddress(address)
  { }
  ...
private:
  string myName, myAddress;
};

Initializer lists are both shorter and often more efficient than assignments. With assignments, a variable is first declared and given a default value that is then overwritten. With initializer lists, the variable is created with the desired value directly. This can be significantly more efficient when variables are for complex user types.

Use explicit with single-parameter constructors to avoid accidental implicit type conversions.

Consider this example from Stroustrup's The C++ Programming Language:

class String {
public:
  String(int n); // initialize a String with n bytes
  String(const char *p); // initialize a String with a C string
  ...
};
...
  String s = 'a';

Presumably the intention was to construct a String with "a". But because there's a character not a string, C++ instead converts 'a' to an integer, and calls the first String constructor to make a String with 'a' bytes! This is called implicit type conversion.

If you put explicit in front of the first constructor (not the second!), then this unintended conversion won't happen.

The parts of a class definition must be public, protected and private.

This makes it easy to read the class definition as the public interface is of interest to most readers.

Declare class data private.

Classes should encapsulate their data and only provide access to this data by member functions to ensure that data in class objects are consistent.

The exception to the rule is a C-style struct that only contains data members.

Only use the keyword struct for C-style structs.

Use struct for small convenience objects holding related data for each access and transport, e.g., an X-Y coordinate point or the location and size of a rectangle.

Functions that can be implemented using public interface of a class should not be members.

A class definition can be kept small and less prone to change if it only defines the core functionality. Any other functions that can be implemented with this minimal class definition should be implemented as non-member functions. They are still seen as part of the interface of the class.

For example:

class T
{
    T operator+=(const T & right);
};

T operator+(const T & left, const T & right)
{
    T temp(left);
    temp += right;
    return temp;
}

Don't define member functions in the class definition.

When you define a member function in the class definition, you make the function inline. That means that every call to the member function will be replaced by the body of the function. This leads to bloated compiled code.

Except for very short functions, e.g., one-liners, you should only declare member functions inside a class definition. The definitions should be separate. This applies to both regular and templated classes.

For example, instead of

class Example
{
  public:
    double currentTotal()      // a definition
    { 
      ... code to calculate total 
    }
};

you should write

class Example
{
  public:
    double currentTotal();     // a declaration 
};

double Example::currentTotal()
{ 
  ... code to calculate total 
}

For regular classes, definitions should go in a separate .cpp file. For templated classes, definitions should go in the header file, after the class definition.

Statements

Never use gotos.

Gotos violate structured coding principles.

Do not use break in loops.

A break statement is a goto statement in disguise and makes code less readable.

A break statement is fine and often unavoidable in switch statements.

Do not use continue in loops.

A continue statement is a goto statement in disguise and makes code less readable.

All switch statements should have a default label.

Even if there is no action for the default label, include one to show that the programmer has considered values not covered by case labels. If the case labels cover all possibilities, it may be useful to put an assertion there to document the fact that it is impossible to get here. An assertion also protects from a future situation where a new possibility is introduced by mistake.

Do not use do-while loops.

do-while loops are less readable than ordinary while loops and for loops since the conditional is at the bottom of the loop. The reader must scan the entire loop in order to understand the scope of the loop.

In addition, any do-while can easily be rewritten into a while loop or a for loop. Reducing the number of constructs used enhances readability.

Expressions

Do not use literal numbers other than 0, 1 and 2.

Use named constants instead of literal numbers to make the code consistent and easy to maintain. The name of the constant is also used to document the purpose of the number.

Use prefix increment and decrement instead of postfix increment and decrement when the value of the result is not used.

For class objects there may be two different member functions for the postfix and prefix operations. The postfix operation has to keep a temporary return value of the object before changing the object. For built-in objects this does not matter as the compiler will be able to optimise away the temporary value when it is not used.

Use the new cast operators.

Use dynamic_cast, const_cast, reinterpret_cast and static_cast instead of the traditional C cast notation. These document better what is being performed.

Other Issues

Avoid macros.

Most macros can be replaced by constants, enumerations or inline functions. Macros do not provide type safety and debugger support.


Comments? comment icon Send mail to c-riesbeck@northwestern.edu.

Valid HTML 4.01 Strict

Generated 2007-03-29 by Coding Standard Generator version 1.13.