C++代码风格指南
当前页面的内容正在依照C++ style guide的内容进行翻译。如果您熟知页面内容并擅长翻译,欢迎协助改善或校对此页面。
Contents
背景
C++是KlayGE使用的主要编程语言。每个C++程序员都知道,这个语言有着很多强大的特性,但强大的同时也带来了各种复杂性。结果就是更容易出bug,并且难以阅读和维护。
本指南将详细描述各种在写C++代码的过程中该做和不该做的事情,以达到控制复杂性的目的。这些规则一方面让代码库便于管理,另一方面允许开发者高效地使用C++的语言特性。
风格,也称为可读性,是掌控我们C++代码的规则。风格这个术语也许有些不当,因为这些规则远远超过了源代码格式的范畴。
我们管理代码库的一种方式是强制保持一致性。让任何开发者可以很快地看懂和明白其他人的代码非常重要。维护一个统一的风格并遵循规则,意味着我们可以更容易地使用“模式匹配”来推断各种符号是什么意思,以及有哪些不变性。建立一个通用的、必需的范式和模式会让代码容易理解得多。在某些情况下,可能会有很好的理由需要改变一些风格的规则,但为了保证一致性,我们仍然需要保持规则不变。
本指南要解决的另一个问题是应对C++的特性膨胀。C++是一个巨大的语言,有着许多先进特性。有些时候我们约束、甚至禁止使用一些特性。我们这么做可以保持代码简单易懂,并避免那些特性可能带来的多种常见错误和问题。本指南列出了这些特性,并解释为什么要限制使用他们。
注意,本指南不是个C++教程。我们假设读者熟悉这门语言。
头文件
一般来说,每个.cpp文件都应该有一个对应的.hpp文件。也有一些常见的例外,比如单元测试和只包含一个main()函数的小.cpp文件。
正确使用头文件会对你代码的可读性、大小和性能产生巨大的改善。
下面的规则将会指引你通过多种使用头文件的陷阱。
#define防护
所有的头文件都应该同时有“#define防护”和“#pragma once”,以防止多次包含,并能加速编译。符号名的格式是_<FILE>_HPP。比如,文件foo.hpp应该有这样的防护:
#ifndef _FOO_HPP #define _FOO_HPP #pragma once ... #endif // _FOO_HPP
头文件依赖
如果前置声明就足够的话,就不要用#include。
当你包含了一个头文件,你就引入了一个依赖。每次那个头文件改变的时候你的代码就需要重新编译。如果你的头文件包含了其他头文件,那么对那些文件的任一次修改都会需要重新编译所有包含该头文件的代码。因此,包含越少越好,特别是在一个头文件里包含其它头文件。
通过使用前置声明,你可以显著地减少在你的头文件中需要包含的其他头文件数量。比如,如果你的头文件使用了File类,但不需要访问到File类的定义,你的头文件只需要前置声明一个class File,而不用#include "base/file.hpp"。在KlayGE中,所有的类和结构都在"KlayGE/PreDeclare.hpp"中进行了前置声明。你可以在你的.hpp开头#include它。
如何在头文件中使用Foo类而不访问它的定义?
- 可以把数据成员的类型定义成Foo*或Foo&。
- 在函数的声明(但不是定义)中,可以使用Foo类型的参数和/或返回类型。(例外之一是,如果一个参数Foo或Foo const &有非显式的、单参数构造函数,你就需要完整的定义才能支持自动类型转换。)
- 可以声明Foo类型的静态数据成员。这是因为静态数据成员是在类定义之外进行定义的。
另一方面,如果你的类是Foo的子类或有个Foo类型的数据成员,你就必须包含Foo的头文件。
有时候,用指针(用scoped_ptr就更好了)来代替对象成员更有道理。但是,这让代码的可读性变复杂了,并增加了一个性能惩罚。所以如果只是为了减少包含头文件,可以不用做这个转换。
当然,.cpp文件一般需要他们所用的类的定义,所以需要包含一些头文件。
注意:如果在你的源文件中使用Foo符号,你应该自己引入Foo的定义,而不是通过#include或前置声明。不该依赖于不是通过直接包含的头文件带来的符号。一个例外是,如果myfile.cpp中用到了Foo,则可以在myfile.hpp里#include(或前置声明)Foo,而不是在myfile.cpp中。