Graphlet C++ Coding Standards

Comments

In C++ Code, always use C++ style comments. Use C style comments only in headers which are used both in C and C++ code. Here is an example:

//
// This otherwise silly comment has a blank line in the middle
//
// and at the beginning and at the end
//

Naming Conventions

Use _
Use _ in names to separate words, as in
// USE this style
int this_is_the_answer = 42;

// NOT this style
int thisIsATheAnswer = 42;

This will make your program more readable.

Use long and unique names
Use long, descriptive and unique names, the longer the better. Especially global names less then 8 characters are depreciated. Use a unique prefix to identify your externally visible names. This is neccessary to avoid collisions with names in other modules.
Note: Graphlet uses the prefixes GT_ and GT_Tcl.

Type and class names must start with a capital letter
Example: GT_Graph, GT_Tcl_Graph.
All other names must start with a lowercase letter.
Example: (from trees) father, son, next_son.
Classes as substitute for a module concept
Since C++ lacks a module concept, use classes for that. Therefore, avoid C style functions if possible. Use static member functions instead:
class foo {
public:
    static bar ();
}

// Later in the progam ...

foo::bar();

Dont use global C style functions. Static member functions are much safer against collisions.

Header Files

C++ header files must be organized as follows:

Classes

Constructor and Destructor

Each class which has member variables must have a constructor. If there is more than one constructor, consider writing a single method for initializing the class, which is used by the constructors.

Virtual Destructors must be used whenever a class has virtual functions. It is a good idea to make all destructors virtual except for trivial classes.

Notes:

Customization

Use virtual methods whenever appropriate so that derived classes can modify the behavior of objects. In generall, one should provide as many customization options as possible. Here are some hints:

Accessors

Member variables in a class must never be public. Instead, access to member variables must be through dedicated procedures, accessors. The example below shows how to properly implement accessors:

class GT_foo : public GT_bar {

    //
    // Graphlet standard class declaration
    //

    typedef baseclass GT_bar;

private:

    //
    // Member variables
    //

    int the_parameter;
    GT_Complex_Datatype the_solution;

public:

    //
    // Constructors and Destructors
    //
    // The destructor is virtual because the base class GT_bar
    // might have virtual methods.
    //

    GT_foo ();
    virtual ~GT_foo ();

    //
    // Methods which set member variables
    //

    void parameter (const int p) {
        this->the_parameter = p;
    }

    void solution (const GT_Complex_Datatype& s) {
        this->the_solution = s;
    }

    //
    // Methods which retrieve the values of member variables
    //

    inline int parameter () const {
        return this->the_parameter;
    }

    inline const GT_Complex_Datatype& solution () const {
        return this->the_solution;
    }
}

Accesssors must observe the following rules:

Prefix the_
As shown in the example, the accessor methods must have the same name as the variable, without the leading the_.
Simple vs. Complex
Non-simple data types (as implied by GT_Complex_datatype in the above example) must return const reference.
Const Access
Generally, think twice before declaring non const access to a member variable. The above scheme gives the class designer full control on when and where a member variable is changed from outside the class. This control is lost as soon as a non const reference is exported. See also the section on Const Correctness.

Const Correctness

Your classes must be const correct. If you do not know about const correctness, see the C++ literature. The following is a short sketch of what const correctness means. Const correct code uses the const keyword to show that

Examples

Here are some examples of const correct code:

Constant method
The following method is constant:
int SomeClass::x() const {
    return this->the_x;
}

The function x() returns a copy of the value of the member variable the_x. Since it does not change the object, it is declared const.

Obviously, a const method must not call any non-const method on the same object.

Copy constructor
The argument of a copy constructor must be a const reference:
void SomeClass::copy (const SomeClass& object)
{
    *this = object;
}
Here, object is declared as a reference to a constant object of class SomeClass, since object is not changed in copy. This is the default way of declaring a copy constructor.

Note: If copy is called with a temporary object, as in aNewObject.copy(SomeClass(42)); then C++ requires a constant reference in the copy constructor. Modern compilers are able to check that.

Return complex object
Accessor methods must always be declared as
const SomeClass& SomeClass::something()
{
    return this->the_something;
}
The object returned by something() cannot be modified, so side effects are effectively ruled out.

Pitfalls and Hints

A const method may call only other const methods, as illustrated below:

int SomeClass::x() const
{
    return this->x;
}

int SomeClass::y() // NOT const
{
    return this->y;
}

int SomeClass::f() const
{
    return x(0) + y(); // ERROR: y is not declared const
}

int SomeClass::g()
{
    return x(0) + y(); // OK
}

The same holds for operations on a const object:

int SomeOtherClass::g(const SomeClass& object) const
{
    // ERROR: object is const,
    // but y is not const
    return object.x(0) + object.y();
}

It is sometimes necessary to declare a constant and a non-constant version of a function:

Something& SomeClass::something() // NOT const
{
    return this->something;
}

const Something& SomeClass::something() // const
{
    return this->something;
}

Const Casts

C++ allows to cast const away, but that is considered evil and should not be used. It may even happen that the a constant and a non-constant object (such as an STL iterator) are implemented differently !

Defensive Programming

Use defensive programming whenever possible. Defensive programming means: any function must act gracefully if there is an error. For example, functions must check for 0 pointers or empty strings. As a rule of thumb, a function must have a defined behavior for all reasonable parameter values, even unexpected ones.

You should use the assert macro to insert assertions:

#include 

void SomeClass::set (int* x)
{
    assert(x != 0);            
    the_x = x;
}

This causes the program to print an error message and core dump whenever x == 0 at runtime. Of course, assert must only be used to check conditions which must not occur.

Of course, an easier way to avoid checking for 0 is to define the method set as follows:

void SomeClass::set (const int& x)
{
    this->x = x;
}

Another way to deal with that problem are C++ exceptions. An exception is a C++ structure that provides graceful handling of emergency situations. However, several compilers still have problems with their implementation of exception handling, so we will not use exception handling at this time.

Notes:

C++ Portability Issues

Some hints to generate portable code in C++:

C++ Do's and Dont's

Avoid Duplicate Code

Duplicate code must be avoided whenever possible. Pack duplicate code into a single method or function, and call this method or function instead. If you have good reasons that this cannot be done for optimization reasons, consider using an inline function or - in extreme cases - a preprocessor macro. The same should be done with similar code pieces. Use a function or method with an additional parameter or a macro.

Avoiding duplicate code will not only provide a cleaner code, but eases maintenance quote a lot.

Overloading

Use function and operator overloading when appropriate. Especially operator overloading can help to write code that is better to understand.

Notes:

Include Directives

The following conventions should be observed for #include directives:
Never use absolute path names.
Absolute path names are often specific for your system, and at least platform dependend.
Never code version numbers in the path or file names.
If you must use a specific (newer that installed) version of a library, add the following to your GNUMakefile:
  1. Add a compiler flag -I/usr/local/xxx/yyy-version/include to set the search path for include files.
  2. Add a compiler flag -L/usr/local/xxx/yyy-version/lib to set the search path for libraries. It is easier to change the files in your makefile than in your code.
  3. Include your files as usual, that is without absolute paths.
  4. Document that.
Do not use -I.
Do not add -I. to your compiler options. It is neither necessary nor good practice to include the current directory explicitly in the search path. To include a file from the current directory, use #include "file.h" rather than #include .
Subdirectories
As a rule of thumb, if your compiler options already include -Idirectory, never add a subdirectory of directory explicitly. Use the format
#include
instead. This will also help others to find out where file.h comes from.

The C++ Preprocessor

Avoid the preprocessor. Avoid C++ preprocessor macros at al costs. Use C++ const and enum declarations or inline functions instead of preprocessor macros whenever possible. They are much easier to use. Errors in preprocessor macros are often hard to find, and name conflicts between C++ names and preprocessor macros are hard to find.

Note: If you suspect a problem with a preprocessor macro, you can use the compiler flag -E to run only the preprocessor.

In general, one can replace macros by enums, consts and (inlined) functions. const, enum and inline functions are C++ elements and their semantics can be checked by the compiler, whereas macros are preprocessor elements and cannot be checked. The C++ constructs

const unsigned int universal_solution = 42;

enum {
    Color_red,    // dont use red, white, blue for a global name
    Color_white,
    Color_blue
}
Color;

inline int width(int w)
{
    return w+42;
}

are save replacements for

#define UNIVERSAL_SOLUTION 42

#define RED 1
#define WHITE 2
#define BLUE 3

#define width(w) ((w)+42)

If you really need to use a macro, use as much braces as you can think of:

// Correct
#define width(w) ((w)+42)

// WRONG
#define width(w) w+42

Think about 100-width(50).

Pointers versus References

It is often useful and generally safer to use references instead of pointers:

// USE this if you can
void SomeClass::f (const structure& reference);

// Not so much recommended
void SomeClass::method (const structure* pointer);

Notes:

Inline Functions

Use inline functions only for very short and often used functions. Never use them for functions with more than a few lines. When in doubt, use a normal function.

Note:

Local and Global Declarations

Use local declarations whenever possible.
Declarations that need not to be visible outside a class should be declared in the private: section of a class declaration. Also, classes which are used in a single module should be declared as local in this module.
Global variables are considered dangerous.
You must not use global variables in C++.

Avoid Anachronisms

Here is a list of really out-of-date features which should not be used anymore these days:

NULL
C used NULL for nil pointers. C++ uses 0:
// OK
char* x = 0;
// Wrong
char* x = NULL;
void proc (void)
In ANSI C, the synatx for empty parameter is (void). C++ uses the more intuitive ():
// OK
void procedure ();
/ Wrong
void proc (void);
#define CONSTANT
Dont use #define to define constants. Use the const keyword instead; this is generally easier to read and avoids hard-to-track errors.
CAPITAL_LETTERS
Avoid names in capital letters, except for preprocessor macros. They are hard to read and type. Also, this style is reserved for preprocessor macros.
Variable declarations at the beginning of a method
This is an old C feature and is depreciated on C++. Variables should be declarated where they are first used. Note that C++ also supports block-local variables.

Graphlet | Coding Standards