[Home]CppCodingStandards/CppProgrammingPractices

TheSourcery | CppCodingStandards | RecentChanges | Preferences | Index | RSS

Programming Practices

Avoid Global Functions, Data and Classes

One way to avoid global functions and classes.

Example:

  // Instead of declaring:
  void Emc2_myFunc1();
  void Emc2_myFunc2();
  class Emc2MyClass { /* ... */ };

  // Encapsulate the functions using an abstract class:
  class Emc2 {
  public:
    static void MyFunc1();
    static void MyFunc2();
    class MyClass { /* ... */ };
    private:
      virtual Dummy() = 0;   // Make the class abstract
   };

   // Now, functions and classes are accessed by the scope-operator:
   Emc2::MyFunc1();
   Emc2::MyFunc2();
   Emc2::MyClass  my_object;

Placement of Declarations

Local variables can be declared at the start of the function, at the start of a conditional block, or at the point of first use. However, declaring within a conditional block or at the point of first use may yield a performance advantage, since memory allocation, constructors, or class loading will not be performed at all if those statements are not reached.

Switch Statements

Specify a break statement after every case block, including the last one unless multiple labels are used for one selection of code. Always define the default case.

Return Statements

Where practical, there will be only one return from a function or method as the last statement. Otherwise, the number of returns will be minimized. Highlight returns with comments and/or blank lines to keep them from being lost in other code.

Other Flow Control Statements

Expressions

Always use parenthesis to clarify the order of evaluation when three or more operators are used.

Casts

Avoid the use of casts except where unavoidable, since this can introduce run-time bugs by defeating compiler type-checking. Working with third-party libraries often requires the use of casts. When you need to cast, document the reasons. Do not write code which depends on functions that use implicit type conversions. Never convert pointers to objects of a derived class to pointers to objects of a virtual base class. Never convert a const to a non-const.

Literals

Constants are to be defined using const or enum; never using #define. Avoid the use of numeric values in code; use symbolic values instead.

Example:

  const double PI = 3.141259;                     // right
  const char APP_NAME = "ACME Spreadsheet 1.0";   // right

  area = 3.141259 * radius * radius;              // not recommended 
  cout << "ACME Spreadsheet 1.0" << endl;         // not recommended

Explicit Initialization

In general, explicitly initialize all variables before use. Always initialize all pointers either to zero or to an object. Set pointers to zero after delete.

Constructs to Avoid

Example:

  if ( p_record = get_record())   //  This saves you nothing at all

  int i = 0
  while( i < 10 ) {
    cout << i++ << endl; // Use a for loop or put i++ on another line
  }

Macros

If macros must be used, they should be enclosed in parentheses to eliminate ambiguity on expansion.

Example:

  #define MAX( x, y )   ( (x) > (y) ) ? (x) : (y) )

Debug Compile-time Switch

Code used only during development for debugging or performance monitoring should be conditionally compiled using #ifdef compile-time switches. The symbols used are DEBUG and STATS, respectively. DEBUG statements announcing entry into a function or member function should provide the entire function name including the class.

Example:

  #ifdef DEBUG
    cout << "ClassName::MemberName: about to do something" << endl;
  #endif

Memory Management

Always use new and delete instead of malloc/calloc/realloc and free. Allocate memory with new only when necessary for variable to remain after leaving the current scope. Always use the delete[] operator to deallocate arrays. After deletion, set the pointer to zero, to safeguard possible future calls to delete.

Required Member Functions

Class Constructors

All constructors should initialize all member variables to a known state. This implies that all classes should have a default constructor (i.e., <nop>MyClass? ();) defined. Providing a deep copy constructor is strongly recommended. If the programmer wishes to not fully implement a copy constructor, then a stub copy constructor should be written and placed in the private section so no one will accidentally call it. A class which uses "new" to allocate instances managed by the class, must define a copy constructor. Do not do any real work in an object's constructor. Inside a constructor initialize variables only and perform only actions that can't fail.

Class Destructors

All classes which allocate resources which are not automatically freed should have a destructor which explicitly frees the resources. Since any class may someday be used as a base class, destructors should be declared virtual, even if empty.

Argument Passing

If the argument is small and will not be modified, use the default pass by value. If the argument is large and will not be modified, pass by const reference. If the argument will be modified, pass by reference.

Example:

  void A::function( int not_changed );      // default: pass by value

  void B::function( const C& r_big_object ) // pass by const reference

  void C::function( int& r_result );        // pass by reference

Default Arguments

Where possible, use default arguments instead of function overloading to reduce code duplication and complexity.

Overriding Virtual Functions

When overriding virtual functions in a new subclass, explicitly declare the functions virtual. Although not normally required by the compiler, this aids maintainability by making clear that the function is virtual without having to refer to the base class header file.

Function Reentrancy

Functions should not keep static variables that prevent a function from being reentrant. Functions can declare variables static. Problems happen when the function is called one or more times at the same time. This can happen when multiple threads are used or from a signal handler. Using the static buffer caused results to overlap and become corrupted.

Const Member Functions

A member function that does not affect the state of an object (its instance variables) is to be declared const. If the behavior of an object is dependent on data outside the object, this data is not to be modified by const member functions.

Example:

  func(...) const {
    code here
  }

This allows these functions to be called for objects which were either declared as const or passed as const arguments.

It is recommended that all member function parameters be declared const, if at all possible.

Example:

  MyFunc(const int i) {
    some code
  }

Member Function Return Types

A public member function must never return a non-const reference or pointer to member data. A public member function must never return a non-const reference or pointer to data outside an object, unless the object shares the data with other objects. By allowing a user direct access to the private member data of an object, this data may be changed in ways not intended.

Referencing Non-C++ Functions

Use the extern "C" mechanism to allow access to non-C++ (not just C) functions. This mechanism disables C++ name mangling, which allows the linker to resolve the function references.

Example:

  extern "C" {
    void a_function();       // single non-C++ function prototype 
  }

  extern "C" {
  #include "functions.h"     // library of non-C++ functions 
  }

NULL Pointer

Use the number zero (0) instead of the NULL macro for initialization, assignment, and comparison of pointers. The use of NULL is not portable. Cast when appropriate (e.g., (char*)0).

Enumerated Types

Use enumerated types instead of numeric codes. Enumerations improve robustness by allowing the compiler to perform type-checking, and are more readable and maintainable.

Terminating Stream Output

Use the iostream manipulator endl to terminate an output line, instead of the newline character \n or \n\r. In addition to being more readable, the endl manipulator flushes the output buffer and automatically performs platform dependent linefeed/carriage return translation.

Object Instantiation

Where possible, move object declarations and instantiations out of loops, using assignment to change the state of the object at each iteration. This minimizes overhead due to memory allocation from the heap.

Encapsulation

Instance attributes of a class never be declared public. Open access to internal variables exposes structure and does not allow methods to assume values are valid. Putting variables in the private section is preferable over the protected section, for more complete encapsulation. Use get and set methods in either protected or public if needed.

Miscellaneous

To determine to complexity of a routine roughly, hand calculate the Decision Point number. Decision points are calculated using the following formula:

A routine with a Decision Point number greater than a 10 score may be too complicated and may need to be refactored.

Portability Concerns

Error Checking

Check every system call for an error return, unless you know you wish to ignore errors. Include the system error text and error numbers, if available, for every system error message.


TheSourcery | CppCodingStandards | RecentChanges | Preferences | Index | RSS
Edit text of this page | View other revisions
Last edited September 15, 2005 8:59 pm by JonLambert (diff)
Search:
All material on this Wiki is the property of the contributing authors.
©2004-2006 by the contributing authors.
Ideas, requests, problems regarding this site? Send feedback.