背景
本规范的目的是通过详细阐述如何进行C++来规避其复杂性,使得代码在有效使用C++语言特性的同时还易于管理。
C++包含大量高级特性的巨型语言,某些情况下,我们会限制甚至禁止使用某些特性使代码简化,避免可能导致的各种问题。
命名规范
通用命名规则
类型名
Line, SavingsAccount
变量名
line, savingsAccount
函数名
函数名必须以小写字母的动词开始并混合大小写,如:
getName(), computeTotalWidth()
常量名
常量名(包括枚举值)必须全部大写并以下划线分隔,如
MAX_ITERATIONS, COLOR_RED, PI
类的私有变量
类的私有变量必须以下划线_作为后缀,如:
class SomeClass {
private:
int length_;
}
名字空间
名字空间的命名必须全部小写,如:
model::analyzer, io::iomanager, common::math::geometry
接口类
接口类的命名以前缀的大写I后跟类名, 如:
class IFoo
{
public:
virtual ~IFoo(){};
virtual void dooo() = 0;
}
模板类型名
模板类型命名必须是单个大写字母,如:
template<class T> ...
template<class C, class D> ...
缩略语
缩略语不必都是大写,如:
exportHtmlSource(); // 这样不好: exportHTMLSource();
openDvdPlayer(); // 这样不好: openDVDPlayer();
全局变量
全局变量命名规则同变量名,只是全局变量必须总是以::来引用,如:
::mainWindow.open(), ::applicationContext.getName()
通用变量
通用变量(如函数参数)的命名应该就是它的类型,且首字母小写,如:
void setTopic(Topic* topic) // 这样不好: void setTopic(Topic* value)
// 这样不好: void setTopic(Topic* aTopic)
// 这样不好: void setTopic(Topic* t)
void connect(Database* database) // 这样不好: void connect(Database* db)
// 这样不好: void connect (Database* oracleDB)
文件名
文件名全部小写,C++文件以.cpp结尾,头文件以.h结尾,文件名与文件里类的名字相同。
非通用命名规则
非通用变量(如局部变量)可以是这样:
Point startingPoint, centerPoint;
Name loginName;
函数名中不应该再包括对象名,如:
line.getLength(); //这样不好: line.getLineLength();
特殊命名规则
指针命名:
Line* line; // 这样不好: Line* pLine;
// 这样不好: Line* linePtr;
枚举常量可以使用一个公共的类型名作为前缀,如:
enum Color {
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE
};
bool类型的变量和函数返回值是bool的函数以is为前缀来命名,如:
isSet, isVisible, isFinished, isFound, isOpen
在某些情况下, 有些比is前缀更适合( has, can, should),如:
bool hasLicense();
bool canEvaluate();
bool shouldSort();
当有属性的情况下, 使用get/ set来存取,如:
employee.getName();
employee.setName(name);
matrix.getElement(2, 4);
matrix.setElement(2, 4, value);
想要计算什么可以使用compute,如:
valueSet->computeAverage();
matrix->computeInverse()
想要想找什么可以使用find, 如:
vertex.findNearestVertex();
matrix.findMinElement();
对象的初始化可以使用initialize , 而不要使用init
GUI组件的命名要把组件类型放到变量的后面,如:
mainWindow, propertiesDialog, widthScale, loginText,
leftScrollbar, mainForm, fileMenu, minLabel, exitButton, yesToggle 等。
复数形式变量命名使用如下:
vector<Point> points;
int values[];
变量具有前缀n表明这个变量代表了对象的个数,如:
nPoints, nLines
No为后缀的变量表明第几个对象,如:
tableNo, employeeNo
迭代变量应该被命名为i, j, k等,如:
for (int i = 0; i < nTables); i++) {
:
}
for (vector<MyClass>::iterator i = list.begin(); i != list.end(); i++) {
Element element = *i;
...
}
对称的命名:
get/set, add/remove, create/destroy, start/stop, insert/delete,
increment/decrement, old/new, begin/end, first/last, up/down, min/max,
next/previous, old/new, open/close, show/hide, suspend/resume, etc.
命名时避免使用缩略语
禁止否定的bool变量名,如:
bool isError; //这样不好: isNoError
bool isFound; //这样不好: isNotFound
异常类命名以Exception作为后缀,如:
class AccessException
{
:
}
注:
1) 不要使用匈牙利命名法
2) Windows定义了很多原有内建类型的同义词(很反感,容易导致类型混乱),如DWORD、HANDLE等等,在调用Windows API时这是完全可以接受甚至鼓励的,但在非调用API时尽量使用原来的C++类型,例如,使用const TCHAR *而不是LPCTSTR;原因:有助于代码的移植。特别是服务器后台开发人员,尽量使用标准的C++类型。
代码书写规范
C++是个特性极其丰富而复杂的语言,本文档旨在提供如何编写高质量C++程序的建议。
原则
正确、简单和清晰第一
软件简单为美(Keep it Sample Software, KISS原则),正确优于速度,简单优于复杂,清晰优于机巧,安全优于不安全。
要避免出现代码“坏味道”,摘录自《重构 改善既有代码的设计》
代码的坏味道
1. 重复代码
2. 过长函数
3. 过大类
4. 过长参数列
5. 发散式变化
6. 霰弹式修改
7. 依恋情结
8. 数据泥团
9. 基本型别偏执
10. switch惊悚现身
11. 平等继承体系
12. 冗赘类
13. 夸夸其谈未来性
14. 令人迷惑的暂时值域
15. 过度耦合的消息链
16. 中间转手人
17. 狎昵关系
18. 异曲同工的类
19. 不完善的程序库类
20. 纯稚的数据类
21. 被拒绝的遗赠
22. 过多的注释
具体内容请参考《重构 改善既有代码的设计》, 在写代码的过程中,加红的几个是特别需要注意的
编译设置
在高警告级别进行编译,使用VC编译器应将警告级别打到最高4级,并设置将警告作为错误
宁要编译时和连接时错误,而不要运行时错误
项目设置include, lib路径使用相对路径,不要使用绝对路径
debug编译生成的是名称后加d ,如(debug是xxxd.exe, release是xxx.exe)
头文件
正确使用头文件可令代码在可读性、文件大小和性能上大为改观。
头文件以.h作扩展名
#define保护
所有头文件 都应该使用#define防止头文件被多重包含(multiple inclusion),命名格式为:<PROJECT>_<PATH>_<FILE>_H_
为保证唯一性,头文件的命名应基于其所在项目源代码树的全路径。例如,项目foo中的头文件foo/src/bar/baz.h按如下方式保护:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_
注:不要使用#pragma once来防止重复包含,因为该指令是vc特有的,它是一个编译器指令,其它的编译器不保证能支持它,而#ifndef是语言层面的, 只要是支持c++的编译器, 都支持,跨平台,兼容性更好。
头文件依赖
使用前置声明(forward declarations)尽量减少.h文件中#include的数量,加快编译速度。
内联函数
叧有当函数叧有10行甚至更少时才会将其定义为内联函数(inline function)。
注:滥用内联将导致程序变慢。
函数参数顺序
定义函数时,参数顺序为:输入参数在前,输出参数在后。
包含文件的名称及次序
将包含次序标准化可增强可读性、避免隐藏依赖(注:隐藏依赖主要是指包含的文件编译)
头文件包含次序如下:C库、C++库、其他库的.h、项目内的.h。
实现文件(如dir/foo.cpp)包含次序如下:
dir2/foo2.h(优先位置,详情如下)
C系统文件
C++系统文件
其他库头文件
本项目内头文件
在包含文件里不要使用.(当前目录)和..(父目录),如:
#include “../abc.h” // 不要这样使用
作用域
命名空间
在.cpp文件中,允许甚至提倡使用未具名命名空间,以避免运行时的命名冲突: namespace {
// .cc 文件中 // 命名空间的内容无需缩进
enum { UNUSED, EOF, ERROR }; // 经常使用的符号
bool AtEof() { return pos_ == EOF; } // 使用本命名空间内的符号EOF
} // namespace
然而,不特定类关联的文件作用域声明在该类中被声明为类型、静态数据成员或静态成员函数,而不是未具名命名空间的成员。像上文展示的那样,不具名命名空间结束时用注释// namespace标识。
不能在.h文件中使用未具名命名空间。
不要在头文件中或者#include之前使用using namespace
局部变量
将局部变量尽可能置于最小作用域内,在声明变量时就将其初始化。
全局变量
禁止class类型的全局变量
内建类型的全局变量是允许的,
当然多线程代码中非常数全局变量也是被禁止的。
永远不要使用函数返回值来初始化全局变量。
原因:c++标准在全局变量的构造函数、析构函数和初始化顺序上没有保证。因此每次生成都可能会有变化,从而导致难以觉察的BUGS。
对于全局的字符串常量,使用C风格的字符串,而不要使用STL的字符串:
const char kFrogSays[] = "ribbet";
虽然允许在全局作用域中使用全局变量,使用时务必三思。大多数全局变量应该是类的静态数据成员,或者当其叧在.cpp文件中使用时,将其定义到未具名命名空间中
注:静态成员变量视作全局变量,所以,也不能是class类型!
如果一定要使用全局变量,请使用设计模式中的单件模式(singleton模式)。
C++类
用小类代替巨类
构造函数
类构造函数的初始化列表中成员变量初始化的顺序应该与在h头文件中声明的顺序一致。不要在构造函数体里进行赋值操作。
对单参数构造函数使用C++关键字explicit。
虚拟函数
避免在构造函数和析构函数中调用虚拟函数
继承
多用组合,少用继承
接口
接口定义如下:
class IFoo
{
public:
virtual ~IFoo(){};
virtual void dooo() = 0;
};
即含有纯虚函数,且实现虚析构函数的类。
接口类主要是为实现多态的。
操作符重载
尽量不要使用操作符重载
存取控制
将数据成员私有化,并提供相关存取函数
声明次序
在类中使用特定的声明次序:public:在private:之前,成员函数在数据成员(变量)前。
定义次序如下:public:、protected:、private:,如果那一块没有,直接忽略即可。
每一块中,声明次序一般如下:
1) typedefs和enums;
2) 常量;
3) 构造函数;
4) 析构函数;
5) 成员函数,含静态成员函数;
6) 数据成员,含静态数据成员。
编写短小函数
函数体尽量短小、紧凑,功能单一
智能指针
如果确实需要使用智能指针的话,scoped_ptr完全可以胜任。在非常特殊的情况下,例如对STL容器中对象,你应该只使用std::tr1::shared_ptr,任何情况下都不要使用auto_ptr。
引用参数
引用传递的参数必须加上const,如果要将参数传出,请使用指针。
缺省参数
禁止使用缺省函数参数。
异常
不要使用C++异常。
异常会导致程序控制流通过查看代码无法确定:函数有可能在不确定的地方返回,从而导致代码管理和调试困难。
异常的实用性可能会刺激开发人员在不恰当的时候抛出异常,戒者在不安全的地方从异常中恢复。
运行时类型识别RTTI
禁止使用RTTI, 单元测试程序除外。
类型转换
使用C++风格的类型转换, 不要使用C风格的类型转换。
1) static_cast:和C风格转换相似可做值的强制转换,或指针的父类到子类的明确的向上转换;
2) const_cast:移除const属性;
3) reinterpret_cast:指针类型和整型或其他指针间不安全的相互转换,仅在你对所做一切了然于心时使用;
4) dynamic_cast:除测试外不要使用,除单元测试外,如果你需要在运行时确定类型信息,说明设计有缺陷(参考RTTI)。
前置自增和自减
对简单数值(非对象)来说,两种都无所谓,对迭代器和模板类型来说,要使用前置自增(自减)。
const的使用
在任何可以使用的情况下都要使用const。
预处理宏
使用宏时要谨慎,尽量以内联函数、枚举和常量代替之,字符串化(##)除外;
0和NULL
整数用0,实数用0.0,挃针用NULL,字符(串)用'/0';
sizeof(sizeof)
用sizeof(varname)代替sizeof(type);
Boost库
只使用Boost中被认可的库。
STL容器
默认时使用vector。否则,选择其他合适的容器
用vector和string代替数组
使用vector(和string::c_str)与非C++API交换数据
在容器中只存储值和智能指针
用push_back代替其他扩展序列的方式
算法和比较器的参数应多用函数对象少用函数
DLL
分配和释放内存
要避免在不同的模块中分配和释放内存,例如,在DLL中分配的内存,必须仍由该DLL来负责释放,而不要交给调用该DLL的程序来释放。
全局变量
不要在DLL中使用全局变量,除非你明确能这样使用。
线程
尽量减少共享数据
在多线程设计中,尽量要减少线程间共享的数据
使用SVN规范
提交代码必须写提交日志
提交代码,代码必须是debug和release都可编译运行的
在修改代码之前,首先update
注释规范
本注释以简单为主,风格遵循doxygen格式,以便于借助doxygen工具可直接生成文档。
注释的确很重要,但最好的代码本身就是文档(自文档),注释是为别人(下一个需要理解你的代码的人)而写的,认真点吧,那下一个人可能就是你!
注释风格
统一使用// 来注释,即双斜杠 + 空格
文件注释
通常,.h文件要对所声明的类的功能和用法作简单说明,.cc文件包含了更多的实现细节或算法讨论,如果你感觉这些实现细节或算法讨论对于阅读有帮助,可以把.cc中的注释放到.h中,并在.cc中指出文档在.h中。
不要单纯在.h和.cc间复制注释,复制的注释偏离了实际意义。
// @brief 简要描述
// @author 作者
类注释
// 类描述
函数注释
// 函数描述
// @param 参数 描述
// @return 返回值 描述
变量注释
注释占一行, 不要这样:
Point centerPoint; // 中心点
应该这样:
// 中心点
Point centerPoint;
TODO注释
// TODO(开发者英文名) 内容
头文件示例:
// $Id: something.h 1 2009-08-20 10:56:07 Zhangwenlong $
// @brief 头文件简要描述
// @author 作者
#ifndef SOMETHING_H_
#define SOMETHING_H_
// 尽量用前置声明,以下是为了演示命名规范
// 尽量在头文件里少包含文件,以免影响编译速度
#include <标准库.h>
#include "第三方库.h"
#include "本项目.h"
// 命名空间全部小写
namespace utility
{
struct Point
{
// 公共变量最后不加下划线_
int x;
int y;
};
enum Color
{
COLOR_RED,
COLOR_WHITE
};
// 接口定义如下:
class IFoo
{
public:
virtual ~IFoo(){};
virtual void doSomething(void) = 0;
};
// 类命名规则:首字母大写
class SomeThing
{
// 如下各块声明顺序如下:
// typedefs
// enums
// 常量
// 构造函数
// 析构函数
// 成员函数
// 成员变量
public:
explicit SomeThing(int value);
// 函数名首字母小写, 参数为:输入在前,输出在后
// 函数描述
// @param 参数 描述
// @return 返回值 描述
void doSomething(const Color& color, Point* point);
const std::string& getName(void) const;
protected:
private:
typedef std::vector<Point> Points;
// 颜色
Color color_;
Points points_;
bool isDone_;
};
}
#endif // SOMETHING_H_
CPP文件示例:
// $Id: something.cpp 1 2009-08-20 10:56:29 Zhangwenlong $
// @brief cpp文件简要描述
// @author 作者
#include "something.h"
#include <c系统文件>
#include <c++系统文件>
#include <其他库文件>
#include "本项目内头文件"
// 可使用匿名命名空间
namespace
{
// 常量定义
static const float PI = 3.14;
// 全局函数
static void foo()
{
// 引用全局变量
float pi = ::PI;
}
} // namespace
SomeThing::SomeThing(int value)
:color_(COLOR_RED), points_(), isDone_(false)
{
}
void SomeThing::doSomething(const Color& color, Point* point)
{
Point centerPoint;
// ...
}