US1oh1 logo

Web eReader

…promoting ultimate readability for the web…

C++ Coding Guidelines

1.0 Introduction

1.1 Document scope

These C++ coding guidelines provides me a standard for writing C++ programs for my projects. The objectives of these guidelines are to produce readable code and avoid many of the problems caused by certain C++ language issues. This is not a C++ language tutorial and does not attempt to address basic coding issues. For this type of information, I consult the latest material I can find. Also, these rules do not attempt to repeat C language convention.

These rules are quite restrictive thereby limiting project risk while acknowledging the pervasive inexperience with the C++ language. Exceptions to any of these rules should only occur only when absolutely neccessary. Several items are specified as language elements to "avoid". This means that there will be reasonable circumstance requiring their use, but they should not be the developers first choice. These rules will be revised as experience warrants.

1.2 Reference sources

These references sources were used as a basis for this document:

1.3 Guideline highlights

This list highlights the most important items from the guidelines that follow. It represents significant differences from standard C++ conventions, or clarifications of certain language ambiguities.

2.0 Design conventions

2.1 Hierarchy

Avoid class hierarchies that are too deep (more than 3 levels) or too wide (more than 3 children).

Deep class hierarchies are difficult to understand. Sometimes, adding a function to an existing class is preferable to deriving an additional class. A wide and shallow class structure may not exploit all the commonality that exists within an abstraction.

2.2 Class structure

A class should be sufficient, complete, and cohesive.

The class is sufficient if it captures enough of the abstraction to provide useful interactions. It is complete if it contains all meaningful characteristics. It is cohesive if the operations are tightly connected by data and functionality.

Avoid classes with too few (< 3) or too many (> 10) operations.

Classes with too few responsibilities should sometimes be merged with other classes. Classes with too much knowledge should sometimes be split into multiple classes.

2.3 Class relationships

A class should not depend in any way on the structure of any other class.

Classes should only obtain information about other classes through the public interface of the other class (encapsulation).

Minimize class coupling.

The less information that one class needs from other classes, the better. It will result in less maintenance and more reuse potential.

Use function overloading when appropriate instead of inheritance.

Don’t inherit if the differences in structure and implementation can be characterized by a small set of type parameters. Inheritance should only support polymorphism.

Use public inheritance to model "is-a" relationships.

Do not use protected inheritance.

This is not a widely used construct. It models "is-a" relationships that restrict access to the base class public members.

Use containment (nested classes) to model "has-a" relationships. Do not use private inheritance.

Containment is when one class has an object (or reference or pointer to an object) of the other class as a member. Private inheritance means "is implemented in terms of" and therefore expresses nothing about the design, only about the implementation.

Do not use multiple inheritance.

Multiple inheritance introduces complexities and potential ambiguities. It’s also not often useful, which means it’s just not worth the trouble.

3.0 Source conventions

3.1 Naming

All names should be readable, comprehensive, and distinct.

Names should be in English implying the full meaning and context of the service and also, unique within the scope.

Successive words in multi-word names should have their first letters capitalized. Acronyms appearing within a longer name should be all uppercase.

Class names should begin with an uppercase letter.

Examples: Message, NetworkElement, PortIO

Variable names and method names should begin with a lowercase alpha character.

Examples: buffer1, lenght(), errorCode, checkQualifiers(), openPortIOSession()

Constructors and destructors will follow the class name convention which specifies begining classnames with an uppercase alpha character.

Each class definition or small group of closely related definitions should be placed in a separate class header file and should have the same name as the most general class. Header files should be suffixed with .hpp.

Examples: Command.hpp, Audit.hpp

Class member implementations should be placed in one or more source files, and the file name will indicate the class and the most important capability being defined. Source files should be suffixed with .cpp.

Examples: Command.cpp, Audit.cpp

3.2 Class definitions

Derived classes should explicitly specify public base class access. See the section on Class Relationships for usage.

class Coupe: public Auto { };
// a Coupe "is a" type of Auto

Do not inline any virtual functions.

Dynamically bound functions should never be inlined. In fact, avoid adding inline directives until after the code is profiled. See the section on Inheritance and Virtual Functions for destructors.

Use the struct keyword to define "classic" data structures containing no member functions.
Class definitions should be organized as follows:

3.3 Class header files

Every header file should be guarded against multiple inclusion with preprocessor directives.

#if !defined(Classname_hpp)
#define Classname_hpp
// header file contents here…
#endif

Only #include header files that are directly needed by the class declaration.

Including other files needlessly increases code size.

Avoid forward class declarations.

They create implementation dependencies.

// forward decl’n to be avoided
// use #include instead
class Size;

class Screen
{
// has a Size pointer
Size *screenSize;
int coordinate;
}

Each class header file should contain the following:

3.4 Class source files

Source files should #include header files instead of using extern declarations. Each class source file should contain the following:

3.5 Class member references

Use this-> only when explicitly required in order to clarify otherwise ambiguous class member references.

3.6 Comments

Use C++-style comments (// in English) except to comment out large portions of code.

3.7 Declarations

Declarations should not appear in the initializer section of a for statement.

This will prevent problems when the ANSI C++ committee changes the variable scope rules.

Every class or struct declaration should have a unique name, and shouldn’t include any object declarations of that type.

// Don’t do this…
struct
Coordinate{int x;
int y;}
myScreenPos;

// Do this instead
struct
Coordinate{int x;
int y;};

// And separately declare all
// objects of that type thus…
Coordinate myScreenPos;

Every function except constructors and destructors should have an explicit return type. The return type (including any *’s and &’s) should appear on a separate line above the name of the function.

This allows tools like vi to easily find the name of the function.

Specify function argument types in the function declaration and definition (ANSI style prototypes).

3.8 Functions

Use reference function arguments & whenever possible. See the section on Const declarations.

This avoids the inefficiency of copying potentially large objects when it is not actually needed.

Do not return non-const references/pointers to member data.

This rule prevents a language construct that would otherwise allow direct access to class members and thus violate encapsulation and information hiding.

3.9 Canonical class

Explicitly provide a copy constructor and assignment operator if objects of the class allocate subsidiary data structures on the heap or consume any other shared resource.

If you do not define them explicitly, the compiler automatically generates a copy constructor and assignment operator each time a class is created. However, they are not adequate under the preceding circumstances. The copy constructor will need to allocate more memory for the new object or explicitly access the shared resource. The assignment operator will need to free the memory or shared resource of the object which is on the left-hand side of the assignment. It is acceptable and safe to always define these functions, even if the default is adequate. However, this creates more work both initially and upon any class structure changes. Additionally, you may choose to explicitly disallow the operators that you don’t want. See the section on Operator Overloading for usage.

3.10 Assignment operator

operator= should first check for assignment to self using this comparison.

This rule averts the disaster that can occur when freeing the same resource you’re trying to assign.

Foo
&Foo::operator=(Foo const &rhs)
{
// immediately check for
// assignment to self
if (this == &rhs)
return *this;

}

Explicitly assign values for all members and base classes in the assignment operator.

operator= should take a const class reference parameter and return *this.

This allows expressions of the form a = b = c to be used and to behave as the built-in assignment operator behaves.

3.11 Constructors

Use initialization lists to initialize class members within constructors, and list members in the same order as they are declared inside the class.

This is more efficient than assignment within the constructor body. It is the only way to initialize reference and const members.

class Foo
{
public:
Foo(char const *s);
private:
String name;
int &ref;
};

Foo::Foo(char const *s,
int &r )
: name(s), ref(r) { }
// constructor call
// String::String(const char*)

3.12 Constants

Use const whenever possible for function return values, individual parameters, and member functions as a whole.

Not specifying const restricts the usage to non-const objects. In contrast, both const and non-const objects can be used if const is specified. Remember, the const keyword means that the function guarantees not to modify the specified object.

size_t
strlen(char const *str)
// strlen won't to modify str
// Note order of the declaration
// specifiers "char const"
// Declarations are to be read
// intuitively right to left

class MyClass
{
public:
// a const member function
int getC(size_t c) const;
// can inspect but not
// change the data members
// of "this" object

};

3.13 Encapsulation

Do not use public data members.

Use inline functions to access and modify simple data members such as state data.

Specify const return type for accessor functions to prevent indirect object modification.

3.14 Inlines

Only inline access functions, and other very short functions.

This avoids the creation of multiple copies resulting from an ignored compiler directive.

Do not inline constructors, destructors, and virtual functions.

This avoids the creation of multiple copies resulting from the need to preserve function addresses.

Do not perform any I/O in inline functions.

This avoids the code bloat caused by including iostream.h.

3.15 NULL

Use 0 instead of the NULL C style convention.

NULL is defined to be (void*)0, which is not guaranteed to be compatible with all C++ pointer types.

3.16 Memory management

Avoid the use of dynamic memory for embedded systems development.

Dynamic memory allocation is encountered less frequently on embedded systems. After all, you can’t just shut down the application in the event you run out of memory. Typically, a pool of pre-allocated buffers are available that are not managed by the language built-ins. The C++ language support for memory management can be overloaded to provide this special handling. Other uses of dynamic memory should be rare.

Overload new and delete for special needs such as management of pre-allocated buffers.

Remember to define delete whenever new has been defined.

Use new and delete instead of malloc() and free(). Use delete [] whenever an array is deleted.

3.17 Preprocessor

Use const and enum instead of #define.

Preprocessor variables are unknown to the C++ compiler and therefore should be avoided.

const int BUFSIZE = 100;
// better than #define,
// but enum is preferred

class Foo
{
// This enum is local
// enums are also guaranteed
// to not consume memory
// This is preferred…
enum { BUFSIZE = 100 };
char buffer[BUFSIZE];
};

Use inline functions for macros instead of #define whenever possible.

Just remember that inline (like register) is just a suggestion to the compiler.

3.18 Inheritance

Every base class should have a virtual destructor.

This rule ensures that the derived class destructor is called when the object is deleted through a base class pointer.

Do not redefine a default parameter for a virtual function.

This rule prevents a confusing situation that results from statically bound default parameters. The bottom line is that you always get the base class default parameter regardless of what the derived class specifies.

Do not redefine an inherited non-virtual function.

This rule prevents calling the wrong version of a function when accessing a derived object via a base class pointer. It also prevents an ugly "hack" of using inheritance without polymorphism (a concept in which a single name may denote objects of many different classes that are related by some common superclass).

3.19 Global namespace

Use enum within class definitions when possible.

The name is then not known outside of that one class (unless the class scope is specified), and the global namespace remains uncluttered. The new namespace feature of C++ will also help when available. It enables a programmer to partition the global namespace.

Use static class members rather than global data when possible.

This results in only one copy of the class member regardless of the number of base or derived objects actually created.

3.20 Code reuse

Use the standard libraries (like iostream) when possible.

3.21 Friend functions

Avoid the use of friend functions.

This weakens encapsulation and information hiding. They are only infrequently necessary for certain binary operator conversion functions (See Operator Overloading), and in exceptional cases where efficiency concerns dictate the avoidance of access functions.

3.22 Operator overloading

Overload operators only where they make sense and in a way that mimics the built-in operators. Explicitly disallow operators that you don’t want.

class MyClass
{
private:
// hide otherwise implicitly
// generated function
MyClass&
operator=(MyClass const &rhs);

};

If an operator is defined which has related operators, they should also be defined.

int
operator==(String const &s1,
String const &s2);
int
operator!=(String const &s1,
String const &s2);
{
// defined as operator==
return !(s1 == s2);
}

If defined, all unary operators and compound assignment operators (+=, *=, etc…) should be defined as class members.

This will suppress an unnecessary implicit type conversion.

If defined, other binary operators should be global functions and may need to be defined as friend functions if they can not be implemented in terms the public class interface.

This allows for implicit type conversion of the left and right hand side of the equation.

3.23 Ellipses arguments

Do not use ellipses.

Using ellipses circumvents type checking and conversions. They should only be used for functions like printf() where it is impossible to predict the number and types of the arguments. The use of <stdargs.h> for these functions is guaranteed to be portable. This replaces the use of <varargs.h> in K&R C.

3.24 Type casting

Do not use type casts unless absolutely necessary.

C++ has strong type checks that should only be overridden for special needs.

3.25 References and pointers

Use references whenever a function parameter is being passed "by reference".

Do not use pointers to "pass by reference" in C++. The use of references eliminates the need to dereference throughout the function, resulting in more readable code. See the section on function argument const declarations.

Use pointers within a function when you want multiple references to the same object or a dynamic data structure.

3.26 Templates

Avoid the use of templates.

They can be tricky to create, use, and read. There are valid reasons to use the available template libraries, but use good judgement. Keep your reviewers and colleagues in mind. Remember to use inheritance instead of templates if the parameter type actually affects the behavior of the class.

3.27 Static object construction

Do not use static objects from different source files within functions called at static constructor time.

The order of static object construction between different source files is undefined. Static object references between files could result in mysterious run-time failures.

3.28 Exception handling

Do not use exception handling.

The non-resumptive nature of throwing exceptions can create problems in embedded systems.