Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

C++ Coding Practices Guide

3.26/5 (36 votes)
20 May 2008GPL35 min read 2  
The article describes C++ coding styles and practices to be followed to develop robust and reliable code that is easily comprehended and maintained by other developers.

Contents

Introduction

To write consistent code comprehended by other developers, you are supposed to follow some coding styles and practices, rather than inventing your own ones. These include naming conventions (on how you name your variables and functions), code and class layout (including tabs, whitespace, brackets placement), imperative const correctness etc... To follow this article, you are supposed to know the basics of OOP and C++. There are some great online resources you may use in addition to this article:

Naming Convention

The first task you encounter when you start to write code is how to name your variables, objects, and functions. The importance of naming conventions can not be underestimated. Providing proper naming will result in self-documenting code, easily comprehended by others. Unless you want to obfuscate the source code, you should follow these guidelines.

Use a name that unambiguously describes a variable or object. A function should contain in its name a verb of the action that it implements.

Linux style (use underscores to separate words, names are lowercase):

C++
int some_variable;
float bar_weight;
unsigned int users_number;
bool is_engine_started;
double circle_area;
double m_circle_area; //class private variable, prepend m_ prefix

void* psome_void_pointer;

const int USERS_NUMBER;

int i, j, n, m, tmp; //some local loops variables, temporary variables

namespace mynamespace;

vector<int> users;
vector<char *> user_names;

class SomeClass;

int do_work(int time_of_day);
float calculate_radius(const Circle& rcircle);
int start_io_manager();
int open_dvd_player();

Windows style (MFC applications, prepend Hungarian prefixes to identify the variable type):

C++
INT nSomeVariable;
FLOAT fBarWeight;
DWORD dwUsersNumber;
BOOL bIsEngineStarted;
DOUBLE dCircleArea;
DOUBLE m_dCircleArea;  //class private variable, prepend m_ prefix

PVOID pSomeVoidPointer;

const INT USERS_NUMBER;

int i, j, n, m, tmp;  //some local loops variables, temporary variables

namespace MyNameSpace;

vector<int> nUsers;
vector<char *> pszUserNames;

class CSomeClass;

INT DoWork(INT nTimeOfDay);
FLOAT CalculateRadius(const& Circle rCircle);
INT StartIOManager();
INT OpenDvdPlayer(); //if abbreviation takes more than
                     // 2 letters do not capitalize the whole word

.NET platform (Hungarian notation, and prefixes are obsolete):

C++
int someVariable;
float barWeight;
unsigned int usersNumber;
bool isEngineStarted;
double circleArea;
double circleArea; //refer to a class private variable
                   // in code as this->circleArea

void^ someVoidPointer;

const int UsersNumber;

int i, j, n, m, tmp;  //some local loops variables, temporary variables

namespace MyNameSpace;

array<int> users;
array<String> userNames;

class SomeClass;

int DoWork(int timeOfDay);
float CalculateRadius(const& Circle circle);
int StartIOManager();
int OpenDvdPlayer();  //if abbreviation takes more than 2 letters
                      // do not capitalize the whole word

Tabs

Tabs are 8 spaces, so the indentations are also 8 characters. The whole idea is to clearly define where a block of control starts and ends, and you'll find it a lot easier to see how the indentation works if you have large indentations. It also has the added benefit of warning you when you're nesting your functions too deep.

C++
//very bad
void FaceDetector::estimate_motion_percent(const vec2Dc* search_mask)
{
  if (search_mask == 0) { m_motion_amount=-1.0f; }
  else 
    {
     unsigned int motion_pixels=0;
     unsigned int total_pixels=0;
      for (unsigned int y = get_dy(); y < search_mask->height()-get_dy(); y++) 
      {
       for (unsigned int x=get_dx(); x < search_mask->width()-get_dx(); x++) 
       {
           total_pixels++;
           if ((*search_mask)(y,x)==1) motion_pixels++;
       }
      }
    m_motion_amount = float(motion_pixels)/float(total_pixels);
   }
}

//very good
void FaceDetector::estimate_motion_percent(const vec2Dc* search_mask)
{
        if (search_mask == 0)
                m_motion_amount = -1.0f;
        else {
                unsigned int motion_pixels = 0;
                unsigned int total_pixels = 0;
                for (unsigned int y = get_dy(); y < 
                           search_mask->height() - get_dy(); y++) {
                        for (unsigned int x = get_dx(); x < 
                                 search_mask->width() - get_dx(); x++) {
                                total_pixels++;
                                if ((*search_mask)(y, x) == 1)
                                        motion_pixels++;
                        }
                }
                m_motion_amount = float(motion_pixels) / float(total_pixels);
        }
}

Spaces, Braces, and Parenthesis

For function and class definitions, it is unambiguous if you put the opening brace on the next line:

C++
void some_function(int param)
{
        //function body
}

class SomeClass
{
        //class body
};

But for ifs, fors, whiles etc..., there are several possibilities for the braces and parenthesis:

C++
if(some_condition)
{
        //...
}

if( some_condition )
{
        //...
}

if ( some_condition )
{
        //...
}

if (some_condition)
{
        //...
}

if (some_condition) {
        //...
}

The preferred way, which is the last, and proposed by K&R, is to put the opening brace last on the line, and put the closing brace first. The reason is to minimize the number of empty lines.

The spaces in math expressions should follow this style:

C++
float a, b, c, d;
int e, f, g, h;

a = (b + d) / (c * d);

e = f - ((g & h) >> 10);    //good
e =f-( (g&h ) >>10);        //bad

Multiple Inclusion Guard

Wrap your header file contents with the multiple inclusion guard to prevent double inclusion:

C++
#ifndef Foo_h
#define Foo_h

// ... Foo.h file contents

#endif

Class Layout

When you declare a class, remember that if you do not provide definitions for:

  • constructor
  • copy constructor
  • assignment operator
  • address-of operator (const and non-const)
  • destructor

they will be provided automatically by C++.

C++
//declaring that class
class SomeClass
{
};

//you get these functions provided automatically
class SomeClass
{
public:
        SomeClass() { }                     //constructor
        ~SomeClass() { }                    //destructor
        SomeClass(const SomeClass &rhs);    //copy constructor
        SomeClass& operator=(const SomeClass& rhs); //assignment operator
        SomeClass* operator&();             //address-of
        const SomeClass* operator&() const; //operators;
};

If you do not intend to provide a copy constructor and an assignment operator, and your class allocates some memory in the constructor and frees it in destructor, declare a copy constructor and an assignment operator as private members to avoid accidental class object cloning and deleting the same memory twice, in the original object and in its copies.

Provide class declarations in the public, protected, and private order. This way, the users of a class will be able to immediately see the public interface they need to use:

C++
#ifndef ClassName_h
#define ClassName_h

class ClassName
{
public:
    ClassName();
    ClassName(const ClassName& classname);
    virtual ~ClassName();

// Operators
    const ClassName& operator=(const ClassName& classname);

// Operations
    int do_work();
    int start_engine();
    int fire_nuke();        

// Access
    inline unsigned int get_users_count() const;        

// Inquiry                
    inline bool is_engine_started() const;

protected:
    //protected functions for descendant classes
        
private:
    //private data and functions
    
    unsigned int m_users_count;
    bool m_is_engine_started;

};

// Inlines
inline unsigned int SomeClass::get_users_count() const
{
    return m_users_count;
}

inline bool SomeClass::is_engine_started() const
{
    return m_is_engine_started;
}

#endif ClassName_h

Keep inlines in header files and outside the class definition, do not garbage the class body.

Const Correctness

Const correctness is to use the keyword const to explicitly declare and prevent the modification of data or objects which should not be modified. Use it at the very outset, as fixing const correctness later will be a nightmare.

You can provide it for data or class member function declarations:

  • const declaration;
  • member-function const;
C++
const int max_array_size = 1024;
max_array_size++;    //compilation error

int a;
int & ra1 = &a;
const int& ra2 = &a;
a = 10;
ra1 = 15;    //ok
ra2 = 20;    //compilation error
//the char data pointed by psour will not be modified
int copy(char* pdest, const char* psour)
{
   //copy data from psour to pdest
}

Declaring a member function with the const keyword specifies that the function is a "read-only" function that does not modify the object for which it is called.

C++
class Foo
{
    //...
    void set(int x, int val);
    int get(int x) const;
    //...
};

const Foo* pobj1;        //modification of the pobj1 is forbidden
int i = pobj1->get();    //you can only call const member-functions
                         //with const pointer to class object
pobj1->set(0, 10);       //compilation error

However, declaring a class member variable as mutable allows it to be modified by a const function.

C++
class SomeClass
{
public:
    void some_function() const;
private:
    mutable int a;
};

void SomeClass::some_function() const
{
    a++;    //you can modify a in const function
}

You can prevent a pointer once assigned to be reassigned to point to another place. This is similar in behaviour to a reference declaration.

C++
Foo foo1, foo2;
int data1[100];
int data2[100];

Foo* const pfoo;
int* const pdata;

pdata = data1;
pfoo = &foo1;

*pdata = 100;        //you can change the data
pfoo->set(0, 10);    //or class object

pdata = data2;       //compilation error
pfoo = &foo2;        //you can not reassign the pointer

You can also declare a const object and a pointer simultaneously:

C++
//neither Foo object can be changed 
//nor pfoo changed to point to another Foo object
const Foo* const pfoo;
const int* const pdata;

Finally, if you provide () or [] operators, provide their const versions also to be used by "read-only" objects.

C++
class SomeClass
{
    ...
    inline int& operator()(int x);      
    inline int operator()(int x) const; 
    inline int& operator[](int x);      
    inline int operator[](int x) const; 
    ...
};

Now, you will be able to use ordinary and const versions of the class object:

C++
SomeClass a;
const SomeClass b;

int i = a(0);
int j = b(0);
a(1) = b(2);
b(3) = a(0);    //compilation error, b object can not be modified

Function Return Value

Functions return values of different types. One of the most typical is a value indicating whether the function succeeded or failed. Such a value can be represented as an error-code integer (negative = failure, 0 = success) or a "succeeded" boolean (0 = failure, non-zero = success).

If the name of a function is an action or an imperative command, the function should return an error-code integer. If the name is a predicate, the function should return a "succeeded" boolean.

C++
int start_engine();           //0 = success, negative = error-codes
bool is_engine_started();     //true = success, false = error

Miscellaneous

Place & or * with a type, not with a variable. This makes it more obvious what the type is.

C++
//good
int& rval;       //&#39;it is&#39; reference int
float* pdata;    //&#39;it is&#39; pointer float

//bad
int &rval;
float *pdata;

Define variables that can not have negative values as unsigned (e.g., unsigned int array_length rather than int array_length). This will help to avoid inadvertent assignment to a negative value.

Reference is a const pointer like int* const p, which once assigned, you can not reassign.

C++
int var1 = 100;
int var2 = 200;

int& ref = &var1;
ref = &var2;        //avoid it!

To provide a constant double pointer for Foo** ppfoo, use the declaration const Foo* const* ppfoo.

Always provide formal arguments in function definitions:

C++
void set_point(int, int);       //ambiguous, what are parameters? avoid it, 
void set_point(int x, int y);   //good.

Comparing a pointer against zero:

C++
void some_function(char* pname)
{
    //bad 
    if (!pname) {
            //...
    }
    
    //error sometimes
    if (pname == NULL) {
            //...
    }
    
    //good
    if (pname == 0) {
            //...
    }
}

Pass by reference or pointer, class objects in function parameters. If you pass by value, the whole copy of the class object will be created, which will be bad if it occupies some megabytes of storage.

C++
//avoid it, pass big_object by reference or pointer
BigObject modify_it(BigObject big_object)
{
    //a local copy of big_object will be created
    ...
    return big_object;
}

//good practice
void modify_it(BigObject& big_object)
{
    //no copy is created, you can modify the object as usually
}

OOP Tips

Never return a pointer or non-const reference to private data members of a class. Do not break the encapsulation principle.

C++
class SomeClass
{
public:
    SomeObject* get_object() { return &m_object; }
    //do not break incapsulation principle
    //m_object can be modified outside the class

    const SomeObject& get_object() { return m_object; }
    //good, the m_object will not be
    //inadvertantly modified outside the class        
private:
    SomeObject* m_object;
};

Always declare a virtual destructor in a public class. If you derive some class from its parent without a virtual destructor, then if you cast back the derived class object pointer to the parent class and later delete the parent class pointer, only parent destructor will be invoked:

C++
class Base
{
    ...
    ~Base();    //should be virtual ~Base()
    ... 
};

class Derived : Base
{
     ...
};

...
Derived* pderived = new Derived();
Base* pbase = (Base *)pderived;
...

delete pbase;    //only ~Base() will be invoked
...

Use friends. If you do not intend some class to be used by the users of your library, but you access private members of that class from other public classes, providing public accessor methods for it is superfluous. Declare your class as a friend of it, and now you will be able to access its private data.

C++
class MainClass
{
    ...
    void some_function();
    ...
    HelperClass* phelper_class;
    ...
};

void some_function()
{
    ...
    int i = phelper_class->m_variable;    //you gain access to private members 
                                          //of HelperClass from MainClass
    phelper_class->m_variable = 10;
    ...
}

class HelperClass
{
    friend class MainClass;
    ...
private:
    int m_variable;
    ...
};

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)