版本号 | 修改时间 | 修改内容 | 修改人 | 审稿人 |
1.0 | 2004-07-22 |
|
白杨 | 田振军 |
1.1 | 2004-08-05 |
|
白杨 | 田振军、马浩军、叶晓峰 |
1.2 | 2004-08-09 |
|
白杨 | 田振军、马浩军、叶晓峰 |
1.3 | 2004-08-10 |
|
白杨 | |
1.4 | 2004-08-10 |
|
白杨 | 广大CSDN上的网友,鸣谢 :-) |
1.5 | 2004-08-28 |
|
白杨 | |
1.6 | 2004-11-22 |
|
白杨 | |
1.7 | 2005-03-30 |
|
白杨 | |
1.8 | 2005-05-04 |
|
白杨 | |
1.9 | 2005-05-11 |
|
白杨 | |
1.10 | 2005-06-06 |
|
白杨 | |
1.11 | 2005-06-07 |
|
白杨 | |
1.12 | 2005-06-28 |
|
白杨 | |
1.13 | 2005-08-20 |
|
白杨 | |
1.14 | 2005-11-08 |
|
白杨 | |
1.15 | 2005-11-14 |
|
白杨 | |
1.16 | 2005-11-16 |
|
白杨 | |
1.17 | 2005-11-21 |
|
白杨 | |
1.18 | 2005-12-02 |
|
白杨 | |
1.19 | 2005-12-25 |
|
白杨 | CCF上的网友,特别感谢smartsl |
1.20 | 2006-04-03 |
|
白杨 | |
1.21 | 2007-02-26 |
|
白杨 | |
1.22 | 2007-07-18 |
|
白杨 | |
1.23 | 2007-11-22 |
|
白杨 | |
1.24 | 2008-01-17 |
|
白杨 | CCF 上的 Jiang Haibin |
1.25 | 2008-03-12 |
|
白杨 | |
1.26 | 2008-06-26 |
|
白杨 | |
1.27 | 2008-07-27 |
|
白杨 | |
1.28 | 2009-02-04 |
|
白杨 | |
1.29 | 2009-03-31 |
|
白杨 | |
1.30 | 2009-04-17 |
|
白杨 | |
1.31 | 2009-05-07 |
|
白杨 | |
1.32 | 2010-06-14 | 陆续对以下部分进行了一些更新和修正:
|
白杨 | |
1.33 | 2010-09-19 |
|
白杨 | |
1.34 | 2011-09-17 |
|
白杨 | |
1.35 | 2012-01-10 |
|
白杨 | |
1.36 | 2012-02-22 |
|
白杨 | |
1.37 | 2012-03-25 |
|
白杨 | |
1.38 | 2013-03-02 |
|
白杨 | |
1.39 | 2013-03-11 |
|
白杨 | |
1.40 | 2013-05-04 |
|
白杨 |
版权声明
概述
针对 C 程序员的快速回顾
语法高亮与字体
字体
语法高亮
文件结构
文件头注释
头文件
内联函数定义文件
实现文件
文件的组织结构
命名规则
类/结构
函数
变量
常量
枚举、联合、typedef
宏、枚举值
名空间
代码风格与版式
类/结构
函数
变量、常量
枚举、联合、typedef
宏
名空间
异常
修改标记
版本控制
自动工具与文档生成
英文版
关于本规范的贯彻实施
术语表
参考文献
C++成长篇
与我联系
常用注释一览
常用英文注释一览
文件头例子
头文件例子
实现文件例
内联函数定义文件例子
类/结构的风格与版式例子
函数的风格与版式例子
RTTI、虚函数和虚基类的实现方式、开销分析和使用指导
C++异常机制的实现方式和开销分析
多处理器环境和线程同步的高级话题
C++0x(C++11)新特性点评
本文档版权归作者所有。您可以以任意形式免费使用本文档的任意部分,并且无需通知作者。作者对使用本文档所造成的任何直接或者间接的损失不负任何责任。 |
对于任何工程项目来说,统一的施工标准都是保证工程质量的重要因素。堪称当今人类最抽象、最复杂的工程——软件工程,自然更加不能例外。 高品质、易维护的软件开发离不开清晰严格的编码规范。本文档详细描述C++软件开发过程中的编码规范。本规范也适用于所有在文档中出现的源码。 除了“语法高亮”部分,本文档中的编码规范都以:
的格式给出,其中强制性规则使用黑色,建议性规则使用灰色 。 |
本节旨在较高层面上快速回顾 C 与 C++ 的主要区别。专门针对 C 思想根深蒂固的老咖和经常需要在 C / C++ 项目间频繁切换的 coder。C 与 C++ 的主要区别包括:
|
字体
|
文字是信息的载体;文字使我们能够把个人的经验和思想长久的保存下来;文字使我们得以站在前人的肩膀上向前发展;文字的诞生标志着人类文明的开始…… 扯的太离谱了?好吧,至少你应该承认:
既然文字如此重要,它的长相自然会受到广泛的关注。如今这个连MM都可以“千面”的年头,字体的种类当然是数不胜数。 然而,前辈先贤们曾经用篆体教导偶们:。想让大家读到缩进、对齐正确一致,而且不出现乱码的源文件,我们就要使用相互兼容的字体。 字体规范如下:
|
几乎所有的现代源码编辑器均不同在程度上支持语法高亮显示的功能。缤纷的色彩不但可以吸引MM们的目光,还可以在很大程度上帮助我们阅读那些奥涩如咒语般的源代码。 统一的语法高亮规则不仅能让我们望色生意,还可以帮助我们阅读没有编码规范,或者规范执行很烂的源码。 所有在文档中出现的代码段均必须严格符合下表定义的语法高亮规范。在编辑源码时,应该根据编辑器支持的自定义选项最大限度地满足下表定义的高亮规范。
|
文件头注释
|
所有C++的源文件均必须包含一个规范的文件头,文件头包含了该文件的名称、功能概述、作者、版权和版本历史信息等内容。标准文件头的格式为:
如果该文件有其它需要说明的地方,还可以专门为此扩展一节,节与节之间用长度为80的“=”带分割:
每行注释的长度都不应该超过80个半角字符。还要注意缩进和对齐,以利阅读。 注意:将多线程和异常时安全性描述放在文件头,而不是类或者函数注释中,是为了体现以下设计思想:同一个模块中的界面,其各方面的操作方式和使用风格应该尽量保持一致。 关于文件头的完整例子,请参见:文件头例子 关于文件头的模板,请参见:文件头注释模板 |
头文件通常由以下几部分组成:
头文件的编码规则:
关于头文件的完整例子,请参见:头文件例子 |
如上所述,在内联函数较多的情况下,为了避免头文件过长、版面混乱,可以将所有的内联函数定义移到一个单独的文件中去,然后再用#include指令将它包含到类声明的后面。这样的文件称为一个内联函数定义文件。 按照惯例,应该将这个文件命名为“filename.inl”,其中“filename”与相应的头文件和实现文件相同。 内联函数定义文件由以下几部分组成:
内联函数定义文件的编码规则:
关于内联函数定义文件的完整例子,请参见:内联函数定义文件例子 |
实现文件包含所有数据和代码的实现体。实现文件的格式为:
实现文件的编码规则:
关于实现文件的完整例子,请参见:实现文件例子 |
由于项目性质、规模上存在着差异,不同项目间的文件组织形式差别很大。但文件、目录组织的基本原则应当是一致的:使外部接口与内部实现尽量分离;尽可能清晰地表达软件的层次结构…… 为此提供两组典型项目的文件组织结构范例作为参考:
|
如果想要有效的管理一个稍微复杂一点的体系,针对其中事物的一套统一、带层次结构、清晰明了的命名准则就是必不可少而且非常好用的工具。 活跃在生物学、化学、军队、监狱、黑社会、恐怖组织等各个领域内的大量有识先辈们都曾经无数次地以实际行动证明了以上公理的正确性。除了上帝(设它可以改变世间万物的秩序)以外,相信没人有实力对它不屑一顾 在软件开发这一高度抽象而且十分复杂的活动中,命名规则的重要性更显得尤为突出。一套定义良好并且完整的、在整个项目中统一使用的命名规范将大大提升源代码的可读性和软件的可维护性。 在引入细节之前,先说明一下命名规范的整体原则:
类/结构
|
除了异常类等个别情况(不希望被用户看作一个普通的、正常的类之情况)外,C++类/结构的命名应该遵循以下准则:
不同于C++类的概念,传统的C结构体只是一种将一组数据捆绑在一起的方式。传统C结构体的命名规则为:
|
|
变量应该是程序中使用最多的标识符了,变量的命名规范可能是一套C++命名准则中最重要的部分:
|
C++中引入了对常量的支持,常量的命名规则如下:
|
枚举、联合及typedef语句都是定义新类型的简单手段,它们的命名规则为:
|
|
C++名空间是“类”概念的一种退化(大体相当于只包含静态成员且不能实例化的类)。它的引入为标识符名称提供了更好的层次结构,使标识符看起来更加直观简捷,同时大大降低了名字冲突的可能性。 名空间的命名规则包括:
|
代码风格的重要性怎么强调都不过分。一段稍长一点的无格式代码基本上就是不可读的。 先来看一下这方面的整体原则:
类/结构
|
类是C++中最重要也是使用频率最高的新特性之一。类的版式好坏将极大地影响代码品质。
关于类声明的例子,请参见:类/结构的风格与版式例子 关于类声明的模板,请参见:类声明模板 |
函数是程序执行的最小单位,任何一个有效的C/C++程序都少不了函数。
关于函数的例子,请参见:函数的风格与版式例子 关于函数的模板,请参见:函数模板 |
|
|
宏是C/C++编译环境提供给用户的,在编译开始前(编译预处理阶段)执行的唯一可编程逻辑。
|
// Others
|
new 时的异常
C++ 标准(ISO/IEC 14882:2003)第 15.2 节中明确规定,在使用 new 或 new[] 操作创建对象时,如对象的构造函数抛出了异常,则该对象的所有成员和基类都将被正确析构,如果存在一个与使用的 operator new 严格匹配的 operator delete,则为这个对象所分配的内存也会被释放。例如:
class CSample { CSample() { throw -1; } static void* operator new(IN size_t n) { return malloc(n); } static void operator delete(IN void* p) { free(p); } static void* operator new(IN size_t n, IN CMemMgr& X) { return X.Alloc(n); } // 缺少匹配的 operator delete }; void Function(void) { CSample* p1 = new CSample; // 有匹配的 operator delete,为 p1 分配的内存会被释放 CSample* p2 = new(iMyMemMgr) CSample; // 没有匹配的 operator delete,内存泄漏!为 p2 分配的内存永远不会被释放 } // 编译器实际生成的代码像这样: void Function(void) { CSample* p1 = CSample::operator new(sizeof(CSample)); try { p1->CSample(); } catch(…) {CSample::opertaor delete(p1); throw; } CSample* p2 = CSample::operator new(sizeof(CSample), iMyMemMgr); p2->CSample(); } |
这里顺便提一句,delete 操作只会匹配普通的 operator delete(即:全局或类中的 operator delete(void*) 和类中的 operator delete(void*, size_t)),如果像上例中的 p2 那样使用了一个高度自定义的 operator new,用户就需要自己完成析构和释放内存的动作,例如:
// … p2->~CSample(); CSample::operator delete(p2, iMymemMgr); |
delete 时的异常
C++ 标准中明确规定,如果在一个析构函数中中途返回(不管通过 return 还是 throw),该析构函数不会立即返回,而是会逐一调用所有成员和基类的析构函数后才会返回。但是标准中并没有说明如果这个异常是在 delete 时发生的(即:该对象是由 new 创建的),此对象本身所占用的堆存储是否会被释放(即:在 delete 时析构函数抛出异常会不会调用 operator delete 释放这个对象占用的内存)。
在实际情况中,被 delete 的对象析构函数抛出异常后,GCC、VC 等流行的 C++ 编译器都不会自动调用 operator delete 释放对象占用的内存。这种与 new 操作不一致的行为,其背后的理念是:在构造时抛出异常的对象尚未成功创建,系统应当收回事先为其分配的资源;而析构时抛出异常的对象并未成功销毁,系统不能自动回收它使用的内存(意即:系统仅自动回收确定完全无用的资源)。
例如:如果一个对象在构造时申请了系统资源(比如:打开了一个设备)并保留了 相应的句柄,但在析构时归还该资源失败(例如:关闭设备失败),则自动调用 operator delete 会丢失这个尚未关闭的句柄,导致用户永远失去向系统归还资源或者执行进一步错误处理的机会。反之,如果这个对象在构造时就没能成功地申请到相应资源,则自动回收预分配给它的内存空间是安全的,不会产生任何资源泄漏。
但是应当注意到,如果一个对象在析构时抛出了异常,则这个对象很可能已经处于一个不完整 、不一致的状态。此时访问该对象中的任何非静态成员都是不安全的。因此,应当在被抛出的异常中包含完成进一步处理的足够信息 (比如:关闭失败的句柄)。这样捕获到这个异常的用户就可以安全地释放该对象占用的内存, 仅依靠异常对象完成后续处理。例如:
//! delete 时异常处理的例子 void Function(void) { CSample* p1 = new CSample; // … try { delete p1; } catch (const sampleExp& err) { CSample::operator delete(p1); // 释放 p1 所占用的内存 // 使用 err 对象完成后续的错误处理… } } |
异常的组织
异常类应该以继承的方式组织成一个层次结构,这将使以不同粒度分类处理错误成为可能。
通常,某个软件生产组织的所有异常都从一个公共的基类派生出来。而每个类的异常则从该类所属模块的公共异常基类中派生。例如:
异常捕获和重新抛出
例如:
try { // … } // 公钥加密错误 catch (const CPubKeyCipher::Exp& err) { if (可以恢复) { // 恢复错误 } else { // 完成能做到的事情 throw; // 重新抛出 } } // 处理其它加密库错误 catch (const CryptoExp& err) { // … } // 处理其它本公司模块抛出的错误 catch (const CompanyExp& err) { // … } // 处理 dynamic_cast 错误 catch (const bad_cast& err) { // … } // 处理其它标准库错误 catch (const exception& err) { // … } // 处理所有其它错误 catch (…) { // 完成清理和日志等基本处理… throw; // 重新抛出 } |
异常和效率
对于绝大部分现代编译器来说,在不抛出异常的情况下,异常处理的实现在运行时几乎不会有任何额外开销。相反,很多时候,异常机制比传统的通过返回值判断错误的开销还来得稍微小些。
相对于函数返回和调用的开销来讲,异常抛出和捕获的开销通常会大一些。不过错误处理代码通常不会频繁调用,再说传统的错误处理方式也不是没有代价的。所以错误处理时开销稍大一点基本上不是什么问题。这也是我们提倡仅将异常用于错误处理的原因之一。
更多关于实现细节和效率的讨论,参见:C++异常机制的实现方式和开销分析 和 RTTI、虚函数和虚基类的开销分析和使用指导 等小节。
在代码交叉审查,或使用带完整源代码的第三方库时,经常需要为某些目的修改源码。这时应当为被改动的部分添加修改标记。
|
|
对于为海外用户编写的代码,所有注释都统一使用英文。关于各标准注释的英文模板,请参考:常用英文注释一览 |
纵观 MSDN、unix/linux manaul(man)、wxWindows Doc等享有盛誉的开发文档都是手工或半手工编写的。相反,那些完全由自动工具生成的文档基本上都是被广大程序员唾弃的 。 由此可以看出,以现今的人工智能科技,完全由机器生成的文档,仍然无法满足人类阅读的需要。但是一份注释详实、版式规范的源代码配合一些简单的工具确实可以大大降低文档编写的工作量。从这样的源码中抽取出来的信息,通常只要稍加整理和修改就可以得到一份媲美MSDN的文档了。 详情参见:软件模块用户文档模板 |
像这样一套完整、详细、繁琐又重要的规范,最难的恐怕就是贯彻实施的环节了。有鉴于此,特提供几点意见:
|
术语 | 解释 |
API | 应用程序编程接口 |
UI | 用户界面 |
GUI | 图形用户界面 |
OOP | 面向对象的程序开发 |
IDE | 集成开发环境 |
没有规矩 否成方圆 |
名称 | 作者 | 发布/出版日期 |
C++程序设计语言——特别版 | Bjarne Stroustrup | 2002 |
ANSI/ISO C++ Professional Programmer’s Handbook | Denny Kalev | 1999 |
ISO/IEC 14882 Programming Languages – C++ | the ANSI C++ community | 1998 |
Microsoft MSDN | 微软公司 | 期刊,参照版本为2004年4月 |
linux/unix在线手册(man) | - | - |
wxWindows 2.4.2 Doc | wxWindows | September 2003 |
高质量C++/C编程指南 | 林锐 博士 | 2001年7月24日 |
人月神话——20周年纪念版 | Frederick P. Brooks Jr. | 2002 |
C/C++编程规范(华为) | 苏信南 | 1997-5-5 |
C++编码规范(中兴) | - | - |
前台软件编程细则(中兴) | - | - |
软件评审 | PMT Community | 2002 |
UML 用户指南 | Grady Booch James Rumbaugh Ivar Jacobson |
2001年6月 |
重构——改善现有代码的设计 | Martin Fowler | 2003年8月 |
设计模式 | Erich Gamma 等 | 2000年9月 |
本篇归纳了一C++程序员成长中的各个阶段,以及踏入该阶段的最佳武林秘笈。本篇仅供各位大侠茶余饭后时拍砖用 。 这里只围绕纯粹的C++程序设计语言进行讨论。当然,要成为一个称职的程序员,计算机原理、操作系统、数据库等其它方面的专业知识也是十分重要的。
|