C++编程规范

C++编程规范

修订日期

版本

备注

作者

2010/6/12

1.00

created

jensen

1 引言

1.1 声明

这篇规范主要部分摘自boost库开发小组,另外增加的部分遵循了一些习惯约定。

1.2 概述

I.C++编码应有自己的风格,C++和C在很多地方都不一样,不应照搬C的风格.下面所有列出的规则都是基于以下一些普适原则 :

(1)直白式的代码比起注释来的更有意义

(2)Coders不能为了图方便而牺牲了代码的健壮性和可理解性

(3)格式化规则应当明白直接,花时间考虑如何格式化代码比多花时间编码更有用

(4)规则应该防范那些会引发微妙错误的代码

(5)应该避免无意义的过于烦琐的规则

II.文中将规则分为以下几个栏目分别展开

1. Files and the preprocessor(文件和预处理器)

2. Naming(命令)

3. Expression spacing and bracketing(表达式间隔和括号)

4. Spacing of definitions and declarations(空格定义和声明)

5. Block and statement formatting(块和声明的格式)

6. Declarations and initialization(声明和初始化)

7. Comments and documentation(注释和文档)

8. Class organization(类的组织)

9. Inheritance and run-time polymorphism(继承和运行时)

10. Component documentation(组件文档)

11. Error handling and robustness(错误处理和健壮性)

12. Namespaces(名字空间)

13. Overloading(重载)

14. Type conversions(类型转换)

15. Miscellaneous(杂项)

1.3 说明

本文目的为了统一编码格式建立一系列的路标.如果对编码格式采取宽松的原则,随性之所至,即兴发挥的话,其结果自然是一堆代码垃圾.如果你选择遵守这些指导原则,请抵抗住在编码时"小小的违例"的诱惑.

C++与C在编码上的区别:

Names -- particularly, qualified names, and expressions and declarations in C+++ are often much longer than in C.

Scoping in C++ is more complex than in C.

Types in C++ are semantically far more important than in C.

Local variable definitions in C++ are mixed in with other statements.

C++ includes constructs that don't exist in C.

C++ overloads syntactic elements even more heavily than C.

Semantics of some C++ constructs differ from C.

2 文件和预处理器

2.1 变更日志, 如果有的话,请放在文件的尾部. 因为源程序文件的主要角色还是反映项目当前状态,不是反映变化过程.

2.2 在头文件中使用#include guards防止被include多次.示例如下:

#ifndef _XXX_H_

#define _XXX_H_

#endif

2.3 避免定义预处理的宏 特别是在头文件中(规则2.2例外),请使用enum,const,function template 等避免宏的定义.

2.4 源文件名一律小写 因为不同平台的文件大小写敏感不一样

2.5 源程序应该首先#include对应的头文件 确保每个头文件都可以单独被include,而不需要额外的头文件依赖.

2.6 #include一律出现在文件开头, 如果是头文件请放在#include guards 之后

2.7 尽量避免头文件依赖 如果某个头文件中需要用到某个类的引用或指针,那么只需要一个forward declaration就足够了

2.8 使用相对#include路径. 目录结构最好和工程的namespace结构类似(这一点boost库做的很好)

3 命名

3.1 命名,除了以下提到的规则之外,应当是"全部小写"并使用下划线分割单词.首字缩写的缩写词作为单词同等对待。

3.2 变量命名必须具有一定的实际意义,形式为xAbcFgh, x由变量类型确定,Abc、Fgh表示连续意义字符串,如果连续意义字符串仅两个,可都大写.如OK.(这一点是针对VC的习惯,也可使用第一点)。[注1]

3.3 模版形式参数命名 使用单词首字母大写形式, 如InMixedCase

3.4 Concept[注2]命名和2.2采用同样的命名方式

3.5 用完整的单词或词组命名 循环变量和遍历算子除外. 一个好名字比一段解释该名字含义的注释更好

3.6 避免简写和复合词 Iterator 比 Iter 好. 短单词比被缩短的单词好

3.7 选择表示目的的名字 而不是表示实现的名字,比如定义一个已知设备列表 list<device>, 命名为known_devices而不是device_list.

3.8 布尔变量和布尔函数应当是E文中的谓词短语 这样条件语句读起来就好像是E文中的条件句了. 如if (buffer->is_empty())

3.9 具有side effect[注3]的函数, 应当是E文中的动词短语

3.10 在命名中避免trademark

3.11 数据成员应具有m_前缀,全局变量使用g_前缀,静态成员使用s_前缀。

3.12 短名字应当依照通用惯例. 如i j k用于循环控制变量,p q用于指针和遍历子

3.13 描述对象状态的函数应当是名词短语 如得到container长度的函数应叫size()而不是get_size()

3.14 预处理宏应尽量避免 无法避免的时候,它的命名应当是全大写形式,头文件中使用的宏要么就取一个奇长无比的名字,要么就在使用以后#undef掉(避免名字污染,呵呵)

3.15 禁止使用带有双下划线'__',或是以单下滑线'_'开头的名字

注1: 一些常用的变量命名约定:(使用匈牙利命令方法,更多的请参见MSDN文档)

数据类型

缩写

示例

BOOL

b

bEnable

char

ch

chText

句柄(HANDLE)

h

hWnd

int

n

nCmdShow

指针

p

pData

字符串

sz

szName

WORD

w

wYear

LONG

l

lParam

UINT

u

uParam

DWORD

dw

dwStart

PSTR

psz

pszTip

LPSTR / LPTSTR

lpsz

lpszCmdLine

LPVOID

lp

lpReserved

WPARAM

wParam

LPARAM

lParam

HWND

h

hDlg

HDC

hDC

HINSTANCE

hInstance

HICON

hIcon

float

f

fTmp

String, AnsiString

str

4表达式的间隔、各种括号

4.1 二目运算符 用空格将它和它的操作数分开 如int b = a + 2

4.2 单目后缀运算符,不能有前导空格如f(),a[],i++

4.3 单目前缀运算符,不能紧跟有空格 如*p,!is_right

4.4 ->前后都不能有空格

4.5 使用位运算时请写全括号

4.6 return不是函数不要对return的结果打上括号, throw也是一样

4.7 如果表达式要分开多行,请在某个运算符之前分行 这样下一行很明显能看出是上一行的继续

如int b = a

+ 2;

5 函数和声明的空间

5.1 主名字空间或全局空间中的定义语句顶格 第二个嵌套的名字空间中的定义语句需要 空两格

5.2 定义语句分行书写, 但缩进相同, 只用于以下情形

template<..>语句

被定义的类名或是函数名

函数,语句,类定义时的'{','}'符号

5.3 函数的返回值和所属类名和函数名写在一行

5.4 连续的两个多行定义之间需要用空行隔开

5.5 class bodies中的变量定义需要缩进2-4个空格,类中的成员的多行定义需要进一步的缩进

5.6 函数定义的参数如要分开多行定义, 需要进一步的缩进...

5.7 多行定义和其他代码之间应该用空行隔开

5.8 存取权限说明符(public,private,protected) 在类的声明中缩进1个空格

5.9 模版实例化中忽略(类型参数之间的)空格 模版声明时可以适当(类型参数之间)加入空格. 过于笨重的模版实例应该被typedef

6 块和语句的格式化

6.1 '{'和'}'单独占用一行(鼓励所有函数不超过一屏,这样'{'占一行确实浪费,在某些语句的后面,如if,while,'{'可以同行。)

6.2 '{'和'}'应和该块代码的前一行代码具有相同的缩进

6.3 块内的语句缩进4格

6.4 缩进时使用空格,不要用TAB,否则不同的TAB长度会产生对齐的问题

6.5 附属的流程控制子语句使用单独的行不要这样写 if (...) do_something;

6.6 多行的附属子语句前后必须加'{'和'}'

if(...)

{

// one line

// another

}

6.8 如果同一语句的分行书写,从第二行开始需要缩进,至少2格

7 声明和初始化

7.1 一行只能声明一个名字

7.2 将*,&和类型名放在一起, 不要和对象名放在一起(如写成int* p, 而不是int *p)[注1]

7.3 等到有意义的初值出现时再初始化局部变量. 这样可以防止未被初始化的变量被使用。

7.4 尽可能晚的定义局部变量

7.5 复用一个变量时不要出于不同的目的. 复用变量并不会节约资源,编译器知道一个变量什么时候没有用了. 相反复用变量会把读代码的人搞糊涂.

7.6 尽可能的使用const. 看的人清楚,用的人也明白,使用错误时编译器还能报错

7.7 将const放在他修饰的基类型之后. 如int const n = 5;

注1: 很多人会认为写成int* p是初学者的行为, 其实不然, 我认为原因是这样的, 虽然*号在语法上是p的修饰符, 但是在语意上却是类型int的修饰符, 如果同时按照7.1 所说的去做,就不会出现int* p,q这样的错误

8 注释和文档

8.1 一行注释使用//;多行使用/* */

8.2 长注释应和代码分在不同的行

8.3 所有的函数接口必须写注释(除了拷贝构造函数, 运算符重载, 析构函数等), 描述 信息包括 函数要求, 函数效果(特别是边际效应)...

8.4 函数的注释放在一处就好, 不用在声明和定义两边都写

8.5 注释所有的public和protected成员函数的声明. 那些具有特定意义的函数如拷贝构 造函数,赋值运算符,析构函数就不用了

8.6 对于每一个虚函数, 在还没有被继承的地方注释 注释包括该函数会在何时被调用,如何被调用, 什么时候会需要重载它. 对于非纯虚函数, 写出它缺省实现的效果

8.7 作为类的public interface的一部分的自由函数的声明必须注视

8.8 所有其它函数(private的非虚函数, 在无名namespace中定义的函数)应该在定义的地方写下它的注释. 这是为了避免在头文件中留下实现细节的描述

8.9 在类的声明时尽量简单(不要留太多对使用这个类的用户无意义的信息在声明里)

8.10 在代码中的注释应当致力于解释为什么, 而不是怎么作. 好的注释并不是重复代码中显而易见的事实, 而是引起对代码中微妙的弱点的重视. 明白的代码常常是被注释所 玷污了, 不过对于作者显而易见的东西对于读者来说常常是晦涩的. 一整段的注释要比逐行解释好的多.

8.11 使用正确的E文句子作注释, 要正确拼写, 首字大写, 带上标点

8.12 使用标准的C++词汇, 要写成member function而不是method, 是class template,

而不是template class, data member而不是attribute

8.13 写注释时, 符号名按照代码里的样式书写 符号名在句首同样保持小写, concept名无论何时出现都大写, 函数名后加上()

8.14 能够被public或protected interface访问到的"类的不变式"[注1]应该在类声明之前写好注释.实现细节中的不变式的规格说明应该写在类的实现之前.

8.15 使用'#if 0...#endif'或'//'注释代码,不要使用'/*...*/'(我想是因为/**/不支持嵌套注释吧, 而#if 0...#endif可以嵌套)

8.16 版本封存以后的修改一定要将老语句用/* */ 封闭,不能自行删除或修改,并要在文件及函数的修改记录中加以记录。

注1: 文件开头的注释模板和函数注释模板。

// Copyright(c) 2000-2003 vpro technology ver : 1.0.0.0

// 说明:本文档实现了对一段缓冲区访问的封装

// 创建:jensen 日期: 2010-6-13

// 修改内容: XXXX

// 修改:XXX 日期: XXX

2、函数的注释模板

/*

* 函数说明.将一段缓冲区的二进制内容转换成字符串(十六进制格式)输出

* 参数说明:

* param1[in] -- XXX

* param2[out] -- XXX

* 返回值

* string 输出的字符串

* 备注:

* 。。。。

*/

9 类的组织 (Class organization)

9.1 按照以下顺序组织类的定义 按照用户最为关心的顺序

Public type forward-declarations & typedefs

Public constructors & destructor

Public member functions

-----------------------------------------------------------

Protected type forward-declarations & typedefs

Protected member functions

-----------------------------------------------------------

Private type forward-declarations

Private member functions

Private data members

9.2 所有的数据成员定义为private, 禁止出现protected数据成员

9.3 友员只能访问成员函数

9.4 禁止在class body中进行函数定义(逛当!)

9.5 嵌套类的定义应当尽可能放在被嵌套的class body外, 如果嵌套类属于实现细节, 放入相应的源文件

9.6 复用public private protected关键字, 将不同类型的member分开, 如成员函数和数据成员

9.7 在继承类里就不要重复写virtual关键字了, 可以将它们的声明组织成一组

9.8 成员函数声明是不需要inline关键字的. 放在定义部分就足够了

10 继承和运行时

10.1 除非你真正需要, 应该避免公有接口的继承 . public继承反应的是一种is-a的关系,如果要实现is-implemented-in-terms-of的关系, 用包含或是代理的方法, 而不要用私有继承

10.2 基类应当是抽象的

10.3 最多重写虚函数一次

10.4 继承类的设计者应是基类的客户

10.5 避免显示调用基类版本的虚函数

10.6 非纯的虚函数应当是private的

10.7 在基类中让虚函数纯虚, 除非该虚函数确实需要缺省的功能

注: 这几条规则加在一起, 几乎否定了80%的继承, 也就是说如果你想用继承, 先看看 基类是不是抽象的?(大部分应该不是吧). 私有继承几乎被否定...说明C++不仅仅是OO语言, 不要只想到继承.关于虚函数的规则基本上不理解, 只是感觉用好多态不是了解语法那么简单

11 组织文档

11.1 在每一个组件之前使用一段注释描述它的界面 (组件是一个,或是一组具有相关功能的类和函数的集合, 通常存在于一个头文件中)

11.2 注释的内容应该是描述改组件是什么, 应该如何使用该组件

11.3 为继承类的实现者在共有接口部分写好详细的文档. (避免错误的继承)

11.4 模版的注释文档应当列出在模版(类/函数)的实现中用到了模版参数的那些操作.

11.5 实现细节文档应该与共有接口注释和继承接口注释分开, 最好不要放在头文件中,并包含一个简练,完整的class invariants(类的不变式)的列表

12 错误处理和健壮性

12.1 清楚了解并仔细维护你的class invariants列表

12.2 用适当的持有资源的对象将所有资源的所都封装起来, 这是exception safety(异常安全)的代码的基本要求

12.3 Document transfer-of-ownership

12.4 使用assert()(或是适当的代替品)来检测是否有未满足的precondition(前提)和编程错误. 前提是函数执行前必须为真的一个条件.

12.5 仅仅在没有适当或是有效的方法告知调用者错误情况时使用异常. 通常是资源分配错误或是内存不足或是文件不存在等.

12.6 如果可能的话, 当函数抛出异常时, 它必须没有没有任何效果, 程序状态应该恢复到函数调用之前, 仿佛没有执行函数一样, 这叫strong guarantee

12.7 不能提供strong guarantee的函数至少要提供basic guarantee. 即保持不变量, 没有资源泄漏. 只能提供basic guarantee的函数应当具有明显的注释说明这一点, 写'basic guarantee only'就够了

12.8 Provide the no-throw (failsafe) guarantee on functions used for recovery

12.9 让自定义的异常类继承自std::exception

12.10 不要将那些拷贝构造函数会抛出异常的类型作为异常抛出

12.11. 不要让异常从析构函数中抛出. 如果在堆栈unwinding时抛出异常会导致程序立即终止

12.12 小心std::uncaught_exception()

12.13 不要使用exeption specification, 如果你想要说明你的函数抛出什么异常,用注释就好了

12.14 了解new(nonthrow)的真正效果

12.15 不要为了便捷而抛出异常, 不要用异常来报告那些经常会发生的错误

12.16 用调试器跟踪你写过的/修改过的每一行代码

13 名字空间

13.1 将你所有的代码放入名字空间中

13.2 每一个子模块都应成为一个单独的子名字空间

13.3 当访问别的名字空间的名字时, 最好使用显示限定(而不是在使用前加using)

13.4 避免使用using指示符

13.5 避免导入标准C头文件

13.6 将只用于该文件的名字放入无名名字空间(namspace {...}), 这样就不会和其它源文件中的名字冲突

14 重载

14.1 不同的重载函数应当具有相同的语意

14.2 运算符重载应当符合既定习惯

14.3 参数的缺省值的赋值不应调用构造函数. 可用重载机制代替

14.4 避免重载虚函数

14.5 为保证常量正确性添加重载函数 当函数返回它的某个参数的non-const reference或iterator(或*this)的时候, 应当有相应的一个函数返回const reference或iterator

15 类型转换

15.1 杜绝隐式类型转换

15.2 在构造函数前使用explicit关键字(拷贝构造函数除外)

15.3 不要定义类型转换运算符(像string那样调用c_str()才能得到原始指针)

15.4 带着极端的偏见尽量避免类型转换, 每个类型转换都意味着你要做一些编译器本不允许你做的事情, 危险!

15.5 Isolate type casts where required...

15.6 了解那种转换更加安全. dynamic_cast<>比static_cast<>安全, static_cast<>和const_cast<>和reinterpret_cast<>比C风格的转换都要安全

16 杂项

16.1 尽量使用标准库

16.2 不要使用接受可变参数个数的函数(like printf), 因为它们不是类型安全的

16.3 避免使用std::endl, 除非你真正需要, 大部分情况下 '\n' 就足够了

16.4 对于不需要copy-by-value语义的类, 从boost::noncopyable继承

16.5 小心使用"引用数据成员". 一个"引用数据成员"的存在会使得缺省的赋值操作的语意不起作用, 通常自定义的赋值运算符也都不会起作用, 因为引用不能重新绑定

转载于:https://my.oschina.net/u/160776/blog/27863

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值