现代 C++ 编码规范#

我们使用现代 C++11。智能指针、Lambda 表达式和 C++11 多线程原语是你的朋友。

快速说明#

“标准”的伟大之处在于有许多可供选择:ISOSutter & StroustrupROSLINUXGoogleMicrosoftCERNGCCARMLLVM,以及可能还有数千个其他标准。不幸的是,其中大多数甚至在如何命名类或常量这样基本的问题上都无法达成一致。这可能是因为这些标准通常由于支持现有代码库而带有许多遗留问题。本文档的目的是创建指导,使其尽可能接近 ISO、Sutter & Stroustrup 和 ROS,同时解决它们之间尽可能多的冲突、缺点和不一致之处。

clang-format#

C++ 语法的格式化由 clang-format 工具标准化,其设置已作为本项目的 .clang-format 文件签入。这些设置旨在与下面列出的格式化指南匹配。你可以使用 clang-format 命令行“格式化”文件,或者通过在每次编辑或保存文件时启用 Visual Studio 自动 clang 格式化。所有文件都已按此方式格式化,名为 clang-format 的 github 工作流也将确保所有拉取请求都正确格式化,因此它应该保持整洁。显然,这不包括 Eigenrpclib 等外部代码。

如果你发现 clang-format 中的错误,可以通过使用以下注释对禁用特定代码块的 clang 格式化

// clang-format off
...
// clang-format on

命名约定#

避免在名称上使用任何形式的匈牙利表示法,以及在指针上使用 "_ptr"。

代码元素 样式 注释
命名空间 下划线分隔 (under_scored) 与类名区分
类名 驼峰式 (CamelCase) 与 ISO 推荐的 STL 类型区分(不使用“C”或“T”前缀)
函数名 小驼峰式 (camelCase) 除了 .Net 世界外,小写开头几乎是普遍的
参数/局部变量 下划线分隔 (under_scored) 绝大多数标准推荐此方法,因为 _ 对 C++ 开发人员来说更具可读性(尽管对 Java/.Net 开发人员来说不是很多)
成员变量 下划线后缀 (under_scored_with_) 前缀 _ 被强烈不鼓励,因为 ISO 有关于保留 _ 标识符的规则,因此我们推荐使用后缀
枚举及其成员 驼峰式 (CamelCase) 除了非常老的标准外,大多数都同意此方法
全局变量 g_下划线分隔 (g_under_scored) 你根本不应该有这些!
常量 全大写 (UPPER_CASE) 争议很大,我们只需要在这里选择一个,除非是类或方法中的私有常量, then use naming for Members or Locals
文件名 与文件中类名的大小写匹配 两种方式都有很多优点和缺点,但这消除了自动生成代码中的不一致性(对 ROS 很重要)

头文件#

使用命名空间限定的 #ifdef 来防止多次包含

#ifndef msr_airsim_MyHeader_hpp
#define msr_airsim_MyHeader_hpp

//--your code

#endif

我们不使用 #pragma once 的原因是,如果同一头文件存在于多个位置(这在 ROS 构建系统中可能是可能的!),它就不受支持。

括号#

在函数或方法体内,大括号放在同一行。在此之外,命名空间、类和方法级别使用单独的行。这被称为 K&R 风格,其变体在 C++ 中广泛使用,而其他风格在其他语言中更受欢迎。请注意,如果只有一个语句,则不需要大括号,但复杂的语句使用大括号更容易保持正确。

int main(int argc, char* argv[])
{
     while (x == y) {
        f0();
        if (cont()) {
            f1();
        } else {
            f2();
            f3();
        }
        if (x > 100)
            break;
    }
}

const 和引用#

严格审查你声明为 const 和引用的所有非标量参数。如果你来自 C#/Java/Python 等语言,最常犯的错误是将参数按值传递,而不是 const T&; 尤其是大多数字符串、向量和映射,如果你想将它们作为 const T&;(如果它们是只读的)或 T&(如果它们是可写的)传递。此外,尽可能在方法后面添加 const 后缀。

重写#

重写虚方法时,使用 override 后缀。

指针#

这实际上是关于内存管理的。模拟器有许多性能关键代码,因此我们尽量避免通过大量调用 new/delete 来使内存管理器过载。我们还希望避免在栈上过多地复制东西,因此我们尽可能通过引用传递。但是当对象确实需要比调用栈更长的生命周期时,你通常需要将该对象分配到堆上,这样你就有了指针。现在,如果该对象的生命周期管理将很棘手,我们建议使用 C++ 11 智能指针。但是智能指针确实有开销,所以不要盲目地到处使用它们。对于性能至关重要的私有代码,可以使用裸指针。在与只接受指针类型的遗留系统交互时,例如套接字 API,也经常需要裸指针。但我们尽量封装这些遗留接口,并避免这种编程风格渗透到更大的代码库中。

严格检查你是否可以在任何地方使用 const,例如 const float * const xP。避免在变量名中使用前缀或后缀来指示指针类型,即使用 my_obj 而不是 myobj_ptr,除非在可能有助于更好地区分变量的情况下,例如 int mynum = 5; int* mynum_ptr = mynum;

空值检查#

在 Unreal C++ 代码中,检查指针是否为空时,最好使用 IsValid(ptr)。除了检查空指针之外,此函数还会返回 UObject 是否已正确初始化。这在 UObject 正在进行垃圾回收但仍设置为非空值的情况下很有用。

缩进#

C++ 代码库使用四个空格进行缩进(而不是制表符)。

换行符#

文件应使用 Unix 换行符提交。在 Windows 上工作时,可以通过运行以下命令配置 git,以 Windows 换行符检出文件,并在提交时自动将 Windows 换行符转换为 Unix 换行符

git config --global core.autocrlf true

在 Linux 上工作时,最好通过运行以下命令配置 git,以 Unix 换行符检出文件

git config --global core.autocrlf input

有关此设置的更多详细信息,请参阅此文档

这太短了,对吧?#

是的,这是故意的,因为没人喜欢阅读 200 页的编码规范。这里的目标是只涵盖 GCC 中的严格模式编译和 VC++ 中 4 级警告即错误未涵盖的最重要内容。如果你想了解如何更好地编写 C++ 代码,请参阅 GotWEffective Modern C++ 一书。