CppCodingStandards
C++ 编码规范
请注意,编码规范和指南永远不会是完美的,并且并非每个人都会同意每条指南或命名约定。这些指南和规范的目的是保持源代码的一致性。
通常情况下,根据现有的代码库采取最一致的做法。如果某件事不明显、模棱两可或您认为不正确,请在邮件列表或 IRC 中提出,以便我们澄清并改进本文档。
头文件
将头文件中的 `#include` 指令保持在最低限度。如果可能,优先使用类/结构的转发声明。
当转发声明就足够时,不要使用 `#include`。
虽然应将 include 保持在最低限度,但如果某个内容在文件中使用且无法转发声明,则应显式 include 它。这对于 std::string 等内容尤其重要,这些内容可能从其他来源引入。(cpplint.py 检查此项)
所有文件(头文件和实现文件)都应包含它们需要的所有内容,以使其自给自足。它们不应假定某些内容会被预先包含。
所有实现文件 (.cc) 都应首先包含 `config.h` 和相关的头文件,这些头文件由实现文件引用。
对于 include 本地目录中的头文件,请使用以下形式
#!highlight cpp #include "kernel/communication/Client.h"
对于 include 系统头文件或库头文件,请使用以下形式
#!highlight cpp #include <somelib/some_lib_header.h>
不要创建仅包含其他头文件的头文件。
使用 C++ 头文件,而不是 C 头文件
使用 C++ 头文件,而不是 C 头文件。例如,您应该使用
#!highlight cpp #include <cstdio> #include <cstring>
而不是
#!highlight cpp #include <stdio.h> #include <string.h>
包含保护
所有头文件必须使用包含保护。`#define` 的名称应为从源根目录开始的文件路径,全部大写,并用下划线分隔。
例如,如果您正在编辑文件 `/kernel/communication/Client.h`,您将使用以下内容
#!highlight cpp #ifndef KERNEL_COMMUNICATION_CLIENT_H #define KERNEL_COMMUNICATION_CLIENT_H ... #endif /* KERNEL_COMMUNICATION_CLIENT_H */
请始终在 `#endif` 上添加注释,指示包含保护 `#define` 的标签
绝不在头文件中使用 using namespace
您绝不应在头文件中使用 `using namespace` 指令。
始终在头文件中指定命名空间前缀,即使对于 std:: 符号也是如此。
正确
#!highlight cpp
// /kernel/communication/packet.h
#include <vector>
namespace kernel
{
namespace communication
{
class packet
{
public:
packet();
/** Get internal buffer */
std::vector<uint8_t>& get_buffer();
private:
std::vector<uint8_t> _buffer;
};
} // namespace communication
} // namespace kernel
不正确
#!highlight cpp
// /kernel/communication/packet.h
#include <vector>
using namespace std;
namespace kernel
{
namespace communication
{
class packet
{
public:
packet();
/** Get internal buffer */
vector<uint8_t>& get_buffer();
private:
vector<uint8_t> _buffer;
};
} // namespace communication
} // namespace kernel
甚至更好,请参阅 typedef 部分:)
变量声明和命名规范
变量声明顺序
始终从右向左思考和阅读声明。
如果某个内容是对不可修改数据的引用或指针,则应像这样结构化声明
type [data-const-modifier] [pointer-or-reference] [pointer-const-modifier] symbol-name
例如
#!highlight cpp
std::string my_data; // a string called my_data
char const* ptr = my_data.c_str(); // a pointer to unmodifiable char array named ptr
char const* const c_ptr = my_data.c_str(); // a non-modifiable pointer to
// non-modifiable char array named c_ptr
// a function accepting a reference to a non-modifiable string object
void fun(std::string const& str)
{
...
}
命名空间名称
命名空间应全部小写,优先使用单个单词。如果命名空间必须使用双词,请使用下划线。
可接受的示例
#!highlight cpp ::kernel ::kernel::client ::kernel::client::communication
类名称
FIXME:描述说它们应该是 PascalCased,但示例说它们不应该是。
类名称应为 PascalCased,并应充分描述对象,尽可能减少缩写。在必要时,类名称的缩写部分应全部大写。
应避免使用前缀(例如,在所有类名前面加上 Cls)。
对于抽象基类,鼓励使用形容词命名类,该形容词说明接口的主要目的。例如,可以被信号中断的类的抽象基/接口类可以命名为 `interruptible`
可接受的示例
#!highlight cpp
class sql_client; // describes an object acting as a client to a SQL store
class interruptible; // an interface class describing a type of object that
can be interrupted by some signal
糟糕的示例
#!highlight cpp class SqlClient; // Don't use PascalCased or camelCased names class cls_client; // Don't use useless prefixes! class tool; // Doesn't describe anything. A tool which does '''''what'''''?
类成员变量名称
注意:请参阅作用域,了解为什么所有成员数据都应为私有。
私有类成员数据
私有成员数据变量应以下划线为前缀,并且应全部小写,并用下划线分隔。
示例
#!highlight cpp
class Student
{
public:
Student(std::string const& name,
uint64_t id) :
_name(name),
_id(id)
{}
~Student()
{}
private:
std::string _name; ///< The student's name
uint64_t _id; ///< The student's identifier
}
类方法名称
类方法应小写,并用下划线分隔。至关重要的是,方法名称应指示描述方法执行的操作。
标准化的访问器和修改器方法配对
对于私有成员数据的公共访问器/修改器对,请使用以下命名约定
访问器
对于 const 返回值,将访问器命名为与私有成员数据变量相同,前面加上 "get_"。
对于 setter,在私有成员数据名称前面加上 "set_"。
对于聚合组合成员数据(当成员数据是容器类型时),提供适当的索引访问器,这些访问器与包含的复合数据类型匹配。例如,如果一个类具有私有成员
#!highlight cpp std::vector<teacher*> _teachers;
您应该提供如下 Teacher 指针成员的访问器方法
#!highlight cpp teacher* get_teacher(size_t index);
对于向此类复合成员添加元素,请使用 "add_" 前缀的添加器方法,如下所示
#!highlight cpp void add_teacher(teacher* new_teacher);
继续上一节的示例
#!highlight cpp
class teacher
{
public:
teacher(std::string const &name) :
_name(name)
{}
~teacher()
{}
private:
std::string _name; ///< The teacher's name
};
class student
{
public:
student(std::string const &name,
uint64_t id) :
_name(name),
_id(id)
{}
~student()
{}
/**
* Returns the student's name
*/
std::string const& get_name() const
{
return _name;
}
/**
* Returns the student's id
*/
uint64_t get_id() const
{
return id;
}
/**
* Returns a pointer to the student's homeroom
* teacher or NULL if not set
*/
teacher const& get_homeroom_teacher() const
{
return *_homeroom_teacher;
}
/**
* Returns a reference to a container of
* all the student's teachers
*/
std::vector<teacher*>& get_teachers() const
{
return _teachers;
}
/**
* Returns a specific teacher at a supplied
* index
*/
teacher const& get_teacher(size_t index) const
{
// note: NOT exception safe! Just an example!
return *(_teachers[index]);
}
/**
* Returns a pointer to the student's
* homeroom teacher
*/
void set_homeroom_teacher(teacher* teacher)
{
_homeroom_teacher = teacher;
}
/**
* Adds a new teacher to the container
* of teachers for this student.
*/
void add_teacher(teacher* teacher)
{
_teachers.push_back(teacher);
}
private:
std::string _name; ///< The student's name
uint64_t _id; ///< The student's identifer
teacher* _homeroom_teacher; ///< The student's homeroom teacher
std::vector<teacher*> _teachers; ///< All of the student's teachers
}
==== Private Pure Virtual Implementation Method Names ====
If a public interface requires virtualization be implemented via private, pure-
virtual methods, one should name the pure virtual implementation method the same
as the public method, prefixed with "do_"
For instance:
{{{#!highlight cpp
class engine
{
public:
engine() :
start_time(0)
{}
virtual ~Engine() {}
int start();
private:
virtual int do_start() = 0;
time_t start_time;
};
class v6_engine :public Engine
{
public:
v6_engine() :
engine()
{}
~v6_engine() {}
private:
int do_start();
};
int engine::start()
{
// log the time the engine started
start_time = time(NULL);
// call subclass' private implementation
return do_start_time();
}
int v6_engine::do_start()
{
// start the engine and return the
// results of starting the engine...
}
枚举
枚举的名称应小写,并用下划线分隔。
示例
#!highlight cpp
enum option
{
DEFAULT = 0, // the default option
MINIMUM = 1, // the minimum option
MAXIMUM = 2 // the maximum option
};
option my_option = DEFAULT;
布尔变量
布尔变量应具有指示其“布尔性”的前缀,并且应为动词。
例如,如果您有一个描述某个内容是否有效的变量,请使用
bool is_valid; bool has_drivers; bool can_be_empty;
而不是
bool valid; bool drivers; bool emptiness;
模块/全局变量名称
通常不鼓励使用全局范围的变量,但是此类模块级别或全局变量的命名应小写,并用下划线分隔。
示例
uint32_t my_global_option;
注意
常量不是变量,遵循单独的命名约定
常量名称
常量名称应全部大写,无论常量的作用域如何。
不应使用不必要的类型信息来为常量命名。
可接受的示例
#!highlight cpp
std::string const DEFAULT_DATA_DIRECTORY = "/var";
class client
{
public:
uint32_t const DEFAULT_PORT = 9999;
};
不正确的示例
#!highlight cpp std::string const STR_DEFAULT_DATA_DIRECTORY = "/var"; // Uses unnecessary prefix std::string const default_data_directory = "/var"; // Does not use all uppercase
源文件名和目录结构
目录应与构成程序的命名空间匹配。
头文件应以“.h”结尾,源文件应以“.cc”结尾
源文件和头文件应与文件包含的类和命名空间同名。
示例
假设您正在编码一个名为 `client` 的类,该类存在于 `kernel::communication` 命名空间中。
头文件和源文件应分别为
/kernel/communication/client.h /kernel/communication/client.cc
缩进和格式化
缩进
应从所有源文件中删除 TAB 字符。
对于 Vi(m) 用户,请使用 expandtab 选项
缩进应完全为 2 个空格,在所有缩进级别
除了命名空间和作用域说明符之外,代码的每个块都应缩进一个级别。命名空间 {} 块和作用域说明符(例如 private:)不应缩进。
switch 语句不应缩进 case 标签。
示例
#!highlight cpp
namespace kernel
{
namespace communication
{
enum option
{
OPTION_ONE = 1,
OPTION_TWO = 2
};
bool some_boolean_variable = false;
option some_option = OPTION_TWO;
class client
{
public:
client()
{
if (not some_boolean_variable)
{
switch (some_option)
{
case OPTION_ONE:
// do option one and fall through...
case OPTION TWO:
// do option two
break;
default:
// do default action
}
}
}
}
} // namespace communication
} // namespace kernel
间距
变量声明
在变量的类型和变量的标签(名称)之间使用一个空格。
不要将变量标签对齐到缩进级别,因为这种缩进不可避免地会在修改代码时变得错位。
正确
#!highlight cpp bool is_derived; uint64_t num_derivatives;
不正确
#!highlight cpp bool is_derived; // There should not be more than one space between type and label uint64_t num_sizes; // Do not align variable labels on bool has_size; // indentation levels
指针和引用变量
声明指针变量或参数时,将星号放在类型旁边。这里的想法是保持类型信息在一起。
例如
#!highlight cpp string* foo;
在引用空指针时,使用 `NULL`,而不是 `0`。
声明引用变量或参数时,将 & 符号放在变量名称旁边。例如
#!highlight cpp string& foo;
赋值运算符
赋值运算符前后各留一个空格。
不要将赋值变量对齐到缩进级别,因为这种缩进不可避免地会在修改代码时变得错位。
正确
bool my_bool = false; uint32_t my_int = 0;
my_int += 1;
不正确
bool my_bool= false; // 赋值运算符前面应有一个空格
uint32_t num_sizes = 0; // 不要将赋值对齐到 uint64_t num_red_sizes= 0; // 缩进级别/制表线
比较运算符
在测试比较时,尽可能地具有描述性和冗余性,并在否定运算符之间留一个空格。
例如,最好使用
#!highlight cpp
if (can_print == false) {...};
或
#!highlight cpp
if (not can_print) {...}
而不是
#!highlight cpp
if (!can_print) {..};
设计指南
保持方法/函数简短
长函数有时是合适的,因此未对函数长度设置硬性限制。如果一个函数超过大约 40 行,请考虑是否可以在不损害程序结构的情况下将其分解。
即使您的长函数现在工作正常,其他人几个月后修改它可能会添加新的行为。这可能会导致难以找到的错误。保持函数简短和简单,可以使其他人更容易阅读和修改您的代码。
当您使用某些代码时,可能会遇到很长且复杂的函数。不要害怕修改现有代码:如果使用这样的函数证明很困难,您发现错误很难调试,或者您想在多个不同的上下文中重用它的一部分,请考虑将该函数分解为更小、更易于管理的部分。
总而言之,最好使用小而集中的函数。
避免全局静态类实例
避免全局静态类实例,因为初始化顺序不确定。如果您需要单例对象,请使用名为 singleton() 的公共静态类方法,该方法初始化方法作用域内的静态对象并返回该对象。确保单例类的构造函数为私有,以避免创建该对象的其他方式。
示例
#!highlight cpp
class MySingleton
{
public:
MySingleton& singleton()
{
static MySingleton my_object;
return my_object;
}
private:
MySingleton();
};
自由使用 `typedef`
为了提高可读性,请自由使用 `typedef`,尤其是在类中的 STL 容器类型的情况下。
例如,假设这段代码
#!highlight cpp
class Teacher;
class Student
{
public:
std::vector<Teacher*>& teachers();
private:
std::vector<Teacher*> _teachers;
};
当使用 `typedef` 用于 `std::vector<Teacher *>` 时,上述代码更具可读性和可维护性,如下所示
#!highlight cpp
class Teacher;
class Student
{
public:
typedef std::vector<Teacher*> Teachers;
Teachers& teachers();
private:
Teachers _teachers;
};
`typedef` 使其更易于阅读,并使将来对 Teacher 集合的可能修改更容易(例如,将 `std::vector<>` 更改为 `std::list<>`)
类设计
来自 Scott Meyers、Herb Sutter 和 Andrei Alexandrescu 的一些有用的建议和最佳实践
- 使接口易于正确使用,难以错误使用
- 将类设计视为类型设计
- 优先使用按常量引用传递而不是按值传递
- 不要尝试返回一个对象,而您必须返回一个引用
- 声明数据成员为私有
except in behaviourless aggregates (C-style structs)
- 优先使用非成员非友元函数而不是成员函数
- 声明非成员函数,当类型转换应应用于所有参数时
- 考虑支持非抛出交换
- 明确您正在编写哪种类型的类
- 优先使用最小类而不是单体类
- 优先使用组合而不是继承
- 避免从未被设计为基类的类继承
- 优先提供抽象接口
- 公共继承模拟“是一个”
- 私有继承模拟“以...实现”
- 公共继承是可替代性。继承不是为了重用,而是为了被重用
- 谨慎覆盖
- 考虑使虚拟函数非公共,并将公共函数设为非虚拟
- 避免提供隐式转换
- 不要泄露您的内部结构
- 谨慎使用 Pimpl
- 始终一起提供 new 和 delete
- 如果您提供任何类特定的 new,
provide all of the standard forms (plain, in-place, and nothrow)
始终使用 RIIA 并了解谁拥有内存/资源
不要使用 malloc 和 free。使用 C++ 的 new 运算符与智能指针容器结合使用。
调用 new 时,将返回的指针放入 `boost::shared_ptr<>` 或 `boost::scoped_ptr<>` 中。这样做可确保正确释放资源,并且您无需自己调用 delete。
始终将 shared_ptr<> 对象构造放在单独的代码行上,以避免使用未命名的临时对象出现问题。
示例
#!highlight cpp
// Example of scoped_ptr<> usage for a new'd object
// that should always be deleted at the end of a function
#include <boost/scoped_ptr.hpp>
class my_class
{
public:
my_class() {}
~my_class() {]
void do_something()
{
cout << "did something" << endl;
}
};
void example_scoped()
{
boost::shared_ptr<my_class> my_object(new my_class());
my_object.do_something();
my_class* my_ptr_to_object= my_object.get();
my_ptr_to_object->do_something();
}
对于由多个执行线程或不同所有者共享的对象,请使用 `boost::shared_ptr<>`。
使用 `boost::shared_ptr<>` 实现 Pimpl 习惯用法
有关智能指针的更多信息,请参阅 此参考
尽可能地使事物保持 const
尽可能将不修改底层成员数据的函数声明为 `const`。
将内联方法体移出类定义
不要在类定义中内联方法体,而是以常规方式声明该方法,并在头文件中的类定义下方使用 inline 说明符定义它。
示例
#!highlight cpp
class foo
{
public:
void bar();
};
inline void foo::bar()
{
/* do something */
}
正确使用初始化列表!
使用初始化列表初始化成员数据,而不是在构造函数的函数体中使用赋值操作。
正确
#!highlight cpp
class my_class
{
public:
my_class(uint32_t number_1, uint32_t number_2) :
_number_1(number_1),
_number_2(number_2)
{}
private:
uint32_t _number_1;
uint32_t _number_2;
};
不正确
#!highlight cpp
class my_class
{
public:
my_class(uint32_t number_1, uint32_t number_2)
{
_number_1 = number_1;
_number_2 = number_2;
}
private:
uint32_t _number_1;
uint32_t _number_2;
};
声明顺序
你的类定义应该从其 public: 部分开始,然后是 protected: 部分,最后是 private: 部分。如果其中任何一个部分为空,则省略它。
重用开源库
当面临编写自己的代码和重用已经实现你所需功能的开源库之间的选择时,通常你应该重用开源库。
尽可能使用的库
Boost C++ 库和组件
boost::program_options boost::any boost::thread boost::regex boost::asio boost::smart_ptr
Google Protobuffers gettext C++0x 和 STL C++0x atomic<>
正确使用命名空间
位于特定模块或类别中的代码应该位于指示模块功能的命名空间内。对于此类代码,在头文件和源文件中,代码都应该封装在 namespace { } 块内。
示例
#!highlight cpp
// Header file /kernel/communication/client.h
namespace kernel
{
namespace communication
{
class client
{
...
};
} // namespace communication
} // namespace kernel
// Source file /kernel/communication/client.cc
绝不在命名空间块内放置 include 语句。
异常处理
自由但明智地使用异常。异常应该保留用于捕获异常行为,而不是自然发生或预期的错误。
尽可能缩小 try ... catch 块的作用域。
错误返回/检索
不要返回整数错误或成功代码。
相反,使用一个能够充分描述错误/成功返回的 enum,并帮助清晰地记录代码。
不要使用 #defines。使用 enum。毕竟,我们使用的是 C++,而不是 C。 :)
示例
#!highlight cpp
enum result
{
SUCCESS = 0,
ERROR_IO = 1, // Error occurred doing I/O
ERROR_LOGIC = 2, // There was a logical error...
};
同时考虑使用 boost::system::error_code 对象来处理系统调用的常见错误返回。
大量使用 `assert`
在你的代码中大量使用 assert。
但是,确保你的 assert() 中没有任何副作用!
例如,以下人为的例子具有一个问题副作用
#!highlight cpp
void some_function()
{
size_t x, y;
for (x = 0, y = 0; x < 10; ++x)
{
assert((y = (x + (y - 1))) > x);
cout << y;
}
}
副作用是 y 变量在 assert() 语句中被赋值。根据断言是否从源代码中编译出来,函数的输出是不同的。
使用 C++ 风格的类型转换
避免使用 C 风格的类型转换。使用 C++ 风格的类型转换,它们更明确,有时也更安全。
示例
#!highlight cpp // cast between integer types uint32_t my_number = 100; int32_t my_signed_number = static_cast<int32_t>(my_number);
命名空间
所有代码都应该正确地命名空间化到表示类/函数所在的目录结构的命名空间中。
例如,如果存在一个源文件 `/kernel/client/client.h`,其中包含一个类 "client",则命名空间应该为
namespace kernel { namespace client { class client { ... }; } // namespace client } // namespace kernel
类型
使用 <cstdint> 和 <climit> 中定义的标准整数和限制类型
优先使用显式大小的整数类型,例如 int64_t,而不是非显式大小的整数类型,例如 long。
当你需要一个表示大小、长度或容量的无符号整数类型时,使用 size_t。
当你需要一个表示从 read() 或 write() 调用返回的可能小于 0 的值的带符号整数类型时,使用 ssize_t。
当你需要一个特定于平台的尺寸类型时,使用 size_t 和 ssize_t。
使用 off_t 表示文件位置/偏移量。
使用 ptrdiff_t 表示指针之间的偏移量或差值。
代码文档和注释
注释风格
我们将使用 Doxygen 来生成在线源代码文档。Doxygen 非常通用,支持几种不同的注释风格。为了在代码中保持一致性,以下是推荐的注释风格。
用 at 符号 (@) 代替反斜杠 (\) 来前置 Doxygen 命令。例如:
#!highlight cpp @brief @details
使用 Javadoc 风格的注释来编写多行注释块。例如
#!highlight cpp /** * ...text... */
注意:将使用 JAVADOC_AUTOBRIEF Doxygen 选项。
文件注释
建议记录一个源文件,但如果你想记录一个全局对象(函数、typedef、enum、宏等),则绝对必要。否则,全局对象的文档将不会生成。
应该在文件的开头注释文件。
示例
#!highlight cpp /** * An example header file. * * @details * A more detailed description here. */
类注释
所有类都应该在头文件的类定义之前立即注释。
类方法
类的应该在头文件中注释,而不是在实现文件中。类的 API 应该通过头文件清晰地暴露。描述方法的作用、其参数以及返回值。如果有关实现细节的复杂信息需要记录,请在实现文件中的方法内部进行记录。
另请参阅 #函数和方法注释 部分中的注释。
类成员变量
成员变量应该在头文件中注释,如果合适的话。成员变量可以在变量声明之上注释
#!highlight cpp /** * Brief description of myVar. * More detailed description of myVar that works because of JAVADOC_AUTOBRIEF. */ int my_var;
或者,成员变量可以注释在变量声明的同一行
#!highlight cpp
int my_var; ///< A brief description of my_var
int my_var2; ///< This is a detailed description of my_var2 because it
///< spans multiple lines.
类示例
#!highlight cpp
/**
* A brief description of HolyHandGrenade.
*
* @details
* An even more detailed description of HolyHandGrenade.
*/
class holy_hand_grenade
{
private:
int grenade_size; ///< Specifies if you want a large or small grenade
public:
/**
* Hurls thine Holy Hand Grenade towards thy foe.
*
* @detail
* Mercifully blows thine enemies to tiny bits.
*
* @param[in] count The number of the counting.
*/
void hurl(int count);
...
};
函数和方法注释
每个函数可以有一个 @detail 部分,如果需要,可以更深入地描述该函数。
应该注释每个参数。请尝试使用 @param 命令的方向属性 ([in], [out], [in,out])。
如果可能返回多个返回值,最好使用 @retval 定义每个可能返回的值,而不是使用 @return。如果有很多返回值,或者你只想总结返回值,请使用 @return 描述返回值。
示例
#!highlight cpp
/**
* Returns the sum of two positive integers.
*
* @detail
* Adds x and y and returns the result in z.
*
* @param[in] x First integer
* @param[in] y Second integer
* @param[out] z Holds the result of x+y
*
* @retval 0 success
* @retval 1 failure, ints were not positive
*/
int sum(int x, int y, int *z)
{
if (x < 0 || y < 0)
return 1;
*z = x + y;
return 0;
}