未来是一枚失落的指针,往昔是一片无法删除的内存

Wednesday, 28 November 2007

C++ Coding Conventions

It is easier to write an incorrect program than understand a correct one.
-- Alan J. Perlis


Contents

0. Preface
1. Indentation
2. Comments
3. Naming Conventions
4. The End

0. Preface

How Strict Are These Standards

The goal of this document is to define a set of coding conventions that may help developers write human-readable C++ code. The conventions may be classified into the following 3 categories:

1. Must and must not: these are important rules that must be strictly respected without exception.
2. Should and should not: these are also important rules. They are usually the right (or wrong) way. Developers should try their best to respect them unless they really have a good reason.
3. Alternatives: all alternatives listed in this document are acceptable. Developers may choose one of them to apply.

Basic Rules

1. All should be as understandable as possible.
2. All should be as readable as possible, except when it would conflict with the previous rule.
3. All should be as simple as possible, except when it would conflict with the previous rules.

1. Indentation

Line Length

Developers must not write lines longer than 120 characters. They should keep their lines shorter than 100 or 80 charaters. Moving the bottom scrollbar hundreds of times when reading a source file is never a pleasant experience.

Indentation

Developers must indent their code. How many spaces should be used as the unit of indentation is not enforced, but developers must keep the indentation consistent at least within one file. They should use 4 spaces as the unit of indentation (which is most widely accepted as a convention). They should use blank spaces instead of tabs (which may be 4 spaces or 8 spaces, depending on the IDE settings).

Wrapping Lines

When an expression will not fit on a single line, developers should break it into multiple lines according to these general principles:

1. Break after a comma.
2. Break before an operator.
3. Prefer higher-level breaks to lower-level breaks.
4. Align the new line with the beginning of the expression at the same level on the previous line.
5. If the above rules lead to confusing code or to code that's squished up against the right margin, just indent 8 spaces instead.

Examples of breaking function calls:

// Align the function parameters at the same level.
my_function(long_expression_1,
long_expression_2,
long_expression_3,
long_expression_4,
long_expression_5);
// Indent 8 spaces for the first function call to avoid
// very deep indents, and align the parameters of the
// second function at the same level.
var = my_first_function(
long_expression_1,
my_second_function(long_expression_2,
long_expression_3));


Examples of breaking an arithmetic expression:

// Prefer higher-level breaks to lower-level breaks, and
// align the new line with the beginning of the expression
// at the same level on the previous line.
long_name_1 = long_name_2 * (long_name_3 + long_name_4)
+ 4 * long_name_5;


Examples of long function declarations.

// Align the function parameters at the same level.
void my_function(int first_param,
my_type another_param,
const std::string& yet_another,
const my_second_type* and_still_another);
// Indent 8 spaces to avoid very deep indents.
std::string my_function_with_a_terriblly_long_name(
int first_param,
my_type another_param,
const std::string& yet_another,
const my_second_type* and_still_another);


Examples of class/function declarations with templates:

// Align the class template parameters at the same level.
template <typename SomeType,
typename AnotherType,
typename YetAnotherType,
typename AndStillAnother>
class my_class_template {
// ...
};


Blank Spaces

Binary operators should be separated from their operands by spaces. Unary operators, such as unary minus, increment (++), and decrement (--), should not be separated from their operands. Examples:


a = (a + b) / (c * d);a += c + d;a++;
std::cout << "size is " << size << std::endl;


When declaring or calling a function with multiple parameters, developers must use a blank space to separate the parameters. Examples:

my_function(param_1, param_2, param_3);


2. Comments

Developers should use // ... instead of /* ... */ when commenting their code. This makes it easier to comment out a block of code when debugging.

Developers must write doxygen comments for public APIs (classes, functions, enumerations, typedefs, etc.) that are easy to be misused. Developers should write doxygen comments whenever necessary. The doxygen comment format is not enforced.

For more information about doxygen, check the doxygen project website at http://www.doxygen.org/

Examples of one recommended doxygen comment style:


//! Documentation of this class: what does this class
//! modelize; how should this class be used, etc.
class some_class {

public:

//! Documentation of this function. Don't write comment
//! to repeat code. Write something that helps the users
//! better understand how to use this function correctly.
//! \param param_1 description of the first parameter.
//! \param param_2 description of the second parameter.
//! \return describe the return value.
//! \throws first_exception describe when the first
//! exception will be thrown out.
//! \throws second_exception describe when the second
//! exception will be thrown out.
int my_func(int param_1, const& std::string param_2);

private:

int member1_; //!< Description of the first member.
int member2_; //!< Description of the second member.

//! If the description of this member is enough to
//! occupy multiple lines, then this form of comment is
//! also perfectly acceptable.
int member3_;

}; // class some_class


3. Naming Conventions

First of all, some general guidelines for names:

1. All names (class names, identifier names, etc.), as well as the rest of the code and comments, must be written in English.
2. All user-defined names must not start with an underscore. Some underscored names are reserved by the compiler. Using such kind of names may have potential danger of name conflicts.
3. Do NOT use Hungarian notation in C++.

underscored_names and CamelCase

The following styles are commonly used to write a name consisting of compounded words or phrases:

- write_all_words_in_lower_case_and_join_them_using_underscores.
- IndicateWordBoundariesUsingCapitalization. This style has two variants: lowerCamelCase and UpperCamelCase.

The STL/boost style uses underscored_names, while the Java style and Microsoft style use CamelCase heavily. All these styles are acceptable, but developers must keep the naming convention consistent at least within one project.

Namespaces

To name a namespace:

- STL/boost style uses underscored_names.
- Java style uses lowerCamelCase.
- Microsoft style uses UpperCamelCase.

Type Names

Types define the classification of variables. A type may be an enumeration, a typedef, a class, or a struct. To name a type:

- STL/boost style uses underscored_names.
- both Java style and Microsoft style use UpperCamelCase.

Function Names

To name a function (at global level or at class level):

- STL/boost style uses underscored_names.
- Java style uses lowerCamelCase.
- Microsoft style uses UpperCamelCase.

Identifier Names

To name a variable or a function parameter:

- STL/boost style uses underscored_names.
- both Java style and Microsoft style use lowerCamelCase.

Variables, except when inside a loop, must not be named using a single character. Variables inside a loop may be named as i, j, k, m, or n.

Some additional conventions should be applied to name a member variable within a class or a struct:

- add an 'm_' prefix for non-static member variables, and add an 's_' prefix for static member variables.
- add an underscore suffix for all member variables (static or non-static).

Both the two alternatives are acceptable, but the convention must be consistent within one project.

Template Parameter Names

Developers must use UpperCamelCase to name template parameters. It is recommended that developers use pithy (single charactoer if possible) yet evocative names for template parameter names.

Constants and Macros

Constants and macros must be named using UPPER_CASES_WITH_UNDERSCORES. In addition, all macros within a project must be prefixed by the project's name and an underscore. It is recommended that guard macros (which prevent recursive inclusion of header files) be named using the pattern <PROJECT_NAME>_<HEADER_FILE_NAME>_<CREATED_DATE>__. This pattern helps distinguish guard macros from other macros.

Examples

STL/boost style:

// my_zoo.hpp: STL/boost style
#ifndef MYPROJ_MY_ZOO_HPP_20071128__
#define MYPROJ_MY_ZOO_HPP_20071128__

#define MYPROJ_LOGGING_ENABLED 1

namespace my_proj {

typedef enum {
tigar,
elephant,
lion,
monkey,
} animal_race;

class animal {

public:
explicit animal(const std::string& name);
virtual ~animal() = 0;
const std::string& name() const;
animal_race race() const;
virtual void eat() = 0;

private:
std::string name_;
animal_race race_;

}; // class animal

class my_zoo {

typedef std::map<std::string, animal*> container_type;

public:

explicit my_zoo();
void add(animal* the_animal);

template<class T>
T* find(const std::string& name) {
animal* the_animal = find_animal(name);
return dynamic_cast<T*>(the_animal);
}

private:

animal* find_animal(const std::string& name);

private:

container_type animals_in_zoo_;

}; // class my_zoo

} // namespace my_proj

#endif // MYPROJ_MY_ZOO_HPP_20071128__


Java style:

// MyZoo.hpp: Java style
#ifndef MYPROJ_MY_ZOO_HPP_20071128__
#define MYPROJ_MY_ZOO_HPP_20071128__

#define MYPROJ_LOGGING_ENABLED 1

namespace myProj {

typedef enum {
TIGAR,
ELEPHANT,
LION,
MONKEY,
} AnimalRace;

class Animal {

public:
explicit Animal(const std::string& name);
virtual ~Animal() = 0;
const std::string& getName() const;
AnimalRace getRace() const;
virtual void eat() = 0;

private:
std::string name_;
AnimalRace race_;

}; // class Animal

class MyZoo {

typedef std::map<std::string, Animal*> ContainerType;

public:

explicit MyZoo();
void add(Animal* animal);

template<class T>
T* find(const std::string& name) {
Animal* animal = findAnimal(name);
return dynamic_cast<T*>(animal);
}

private:

Animal* findAnimal(const std::string& name);

private:

ContainerType animalsInZoo_;

}; // class MyZoo

} // namespace myProj

#endif // MYPROJ_MY_ZOO_HPP_20071128__


Microsoft style:

// MyZoo.hpp: Microsoft style
#ifndef MYPROJ_MY_ZOO_HPP_20071128__
#define MYPROJ_MY_ZOO_HPP_20071128__

#define MYPROJ_LOGGING_ENABLED 1

namespace MyProj {

typedef enum {
TIGAR,
ELEPHANT,
LION,
MONKEY,
} AnimalRace;

class Animal {

public:
explicit Animal(const std::string& name);
virtual ~Animal() = 0;
const std::string& GetName() const;
AnimalRace GetRace() const;
virtual void Eat() = 0;

private:
std::string name_;
AnimalRace race_;
}; // class Animal

class MyZoo {

typedef std::map<std::string, Animal*> ContainerType;

public:

explicit MyZoo();
void Add(Animal* animal);

template<class T>
T* Find(const std::string& name) {
Animal* animal = FindAnimal(name);
return dynamic_cast<T*>(animal);
}

private:

Animal* FindAnimal(const std::string& name);

private:

ContainerType animalsInZoo_;

}; // class MyZoo

} // namespace MyProj

#endif // MYPROJ_MY_ZOO_HPP_20071128__


Personally, I prefere the STL/boost style. Because I use heavily STL and boost in my projects, using the same convention makes my code look consistent.

4. The End

这份文档的第一个版本是我2006年6月刚到新公司的时候奉命起草的。那个时候我刚刚离开Java阵营,还拥有一个Java程序员应有的循规蹈距。一年半以后的今天当我重看这份文档的时候,我早已习惯了C++的放荡,因此对文档中的很多限制连我自己都觉得不爽。Java程序员是一群忠厚老实的家犬,而C++程序员却是一群桀骜不驯的野猫。这是一份修改过的版本,贴在这里纪念我由狗到猫的转变。