C++ 编程风格

谷歌C++编程风格

已有项目建议保持项目的风格,保持命名风格一致性比具体用哪种风格更重要。新项目(可参考谷歌编程风格):

  • 类型名: UpperCamelCase

  • 普通函数名: UpperCamelCase

  • 类成员函数: UpperCamelCase

  • 普通变量名: snake_case

  • struct成员变量: snake_case

  • class成员变量: snake_case_

  • 常量名: kUppperCamelCase

  • 枚举名: kUpperCamelCase , 这个类似常量

  • 缩进:统一两个空格,而 public/private/protected 缩进一个空格

  • 缩写处理:缩写按单词处理,例如 HttpServer 而不是 HTTPServer

  • 布尔变量:用 is_ / has_ / can_ / should_ 前缀,例如 is_valid,而不是 validFlag

  • 文件命名:一律 snake_case

  • 命名空间:禁止头文件用 using namespace。在 .cc 文件中可以使用命名空间别名,例如 namespace foo = my_project::foo;

  • 禁止使用宏定义常量(#define),用 constexprconst 代替

  • 单行注释用 //

  • 类、函数、复杂逻辑前建议加简短注释,说明用途而不是实现细节

  • 指针和引用的靠近:例如 char* ptr 表示星号靠近类型,const std::string& name 表示引用符号靠近类型

  • 头文件保护: #ifndef/#define/#endif 形式的 include guard,或 #pragma once

  • 函数参数: 输入参数在前,输出参数在后;尽量使用返回值而不是输出参数

  • 类成员顺序: public → protected → private,并且每个区域内部按类型分组(类型别名、常量、构造/析构、方法、数据成员)

  • 优先使用 std::unique_ptr / std::shared_ptr,避免裸 new/delete

  • 头文件顺序: 自己的 .h → C 系统库 → C++ 标准库 → 其他库 → 本项目其他头文件。每组之间空一行

  • 不鼓励使用异常(throw),推荐返回 Status 或 bool

  • 行宽还是推荐 80

备注

https://google.github.io/styleguide/cppguide.html#Function_Names

  • 大多数函数名:遵循UpperCamelCase,即每个单词的首字母都大写,例如 CalculatePrice()

  • 存取器(Getter/Setter):可以作为特例,使用 snake_case。例如, int count()int get_count()

https://google.github.io/styleguide/cppguide.html#Variable_Names

  • 普通变量:The names of variables (including function parameters) and data members are snake_case,包括了全局变量等,谷歌不规矩全局作用域的全局变量,推荐用 class内部的static变量或者namespace中的全局变量。PS: 如果确实是有这个变量,我个人觉得可以加个 g_ 前缀,很好的一眼区分和普通变量。

  • 类成员变量: Data members of classes, both static and non-static, 和普通变量一样, 但是 下划线结尾,对于static constant class members例外;

  • 结构体成员变量: Data members of structs, both static and non-static, 和普通变量一样, 也没有下划线结尾;

https://google.github.io/styleguide/cppguide.html#Constant_Names

Variables declared constexpr or const, and whose value is fixed for the duration of the program, are named with a leading "k" followed by mixed case, such as kUpperCamelCase. Underscores can be used as separators in the rare cases where capitalization cannot be used for separation.

https://google.github.io/styleguide/cppguide.html#Enumerator_Names

Enumerators (for both scoped and unscoped enums) should be named like constants, not like macros. That is, use kEnumName not ENUM_NAME. such as kOk = 0, kOutOfMemory, .

https://google.github.io/styleguide/cppguide.html#Namespace_Names

Namespace names are snake_case (all lowercase, with underscores between words). 在头文件中,当你在命名空间外使用该命名空间里的东西时,必须使用完整的命名空间名。因为在头文件中,使用不完整的命名空间别名(例如 using namespace some_namespace;)是被严格禁止的。因为会直接被其他 .cc 文件导入了这个。

  • 完整限定(fully qualified):指使用完整的命名空间路径,比如 google::protobuf::Message

  • 不完整别名(unqualified aliases):指像 using namespace std; 这样的语句。它会把整个命名空间的内容导入到当前作用域,这在头文件中是危险的,因为它可能导致包含该头文件的其他代码文件出现意外的命名冲突。

项目举例参考:

// see: 类成员函数名, 普通变量名, 类成员变量名, 常量名, 类型名
// @file: https://github.com/google/googletest/blob/main/googletest/src/gtest-filepath.cc
const char kPathSeparator = '/';
FilePath FilePath::RemoveExtension(const char* extension) const {
  const std::string dot_extension = std::string(".") + extension;
  if (String::EndsWithCaseInsensitive(pathname_, dot_extension)) {
    return FilePath(
        pathname_.substr(0, pathname_.length() - dot_extension.length()));
  }
  return *this;
}

// see: 普通函数命名,变量名
// @file: https://github.com/google/googletest/blob/main/googletest/src/gtest-typed-test.cc
static const char* SkipSpaces(const char* str) {
  while (IsSpace(*str)) str++;
  return str;
}

// see: 函数名,变量名
// @file: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/cc/scheduler/begin_frame_tracker.cc
bool BeginFrameTracker::HasLast() const {
  DCHECK(HasFinished())
      << "Tried to use last viz::BeginFrameArgs before the frame is finished.";
  return current_args_.IsValid();
}

// see: public 缩进 1 个空格
// @file: https://github.com/google/googletest/blob/main/googletest/test/production.h
class PrivateCode {
 public:
  // Declares a friend test that does not use a fixture.
  FRIEND_TEST(PrivateCodeTest, CanAccessPrivateMembers);

  // Declares a friend test that uses a fixture.
  FRIEND_TEST(PrivateCodeFixtureTest, CanAccessPrivateMembers);

  PrivateCode();

  int x() const { return x_; }

 private:
  void set_x(int an_x) { x_ = an_x; }
  int x_;
};

// see: 命名空间的行首无所进,结尾出用注释来显示的指出闭合的是哪个 namespace
// @file: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/cc/scheduler/scheduler.h
namespace perfetto {
namespace protos {
namespace pbzero {
class ChromeCompositorSchedulerStateV2;
}
}  // namespace protos
}  // namespace perfetto

备注

Google 风格将 函数和类型 都归为 UpperCamelCase ,这种做法使得代码库中的所有“可调用实体”(函数和类型构造函数)都以大写开头,形成了一种视觉上的统一。

同时使用 UpperCamelCaselowerCamelCase,有时候可能会让人混淆。Google 风格通过将规则简化为两种主要风格 (UpperCamelCasesnake_case),并且每种风格都有明确的用途,从而避免了这种混淆。

When Google's large-scale C++ projects include a small amount of C code, the C code itself will follow the snake_case naming convention because that's the standard style for the C language community.

C++ Core Guidelines

是目前最权威、最受推崇的 C++ 指南之一。它由 C++ 之父 Bjarne Stroustrup 和其他 C++ 专家共同维护。它的目标不是为了统一代码格式(比如缩进),而是为了指导开发者如何编写更现代、更安全、更高效的 C++ 代码。

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c-core-guidelines

对于命名规则的建议:Rationale: Consistency in naming and naming style increases readability. 就是统一就好。

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rl-name

LLVM Coding Standards

LLVM关于命名风格相关:

https://llvm.org/docs/CodingStandards.html#the-low-level-issues

LLVM 命名风格如下:

  • 类型名: UpperCamelCase, (including classes, structs, enums, typedefs, etc), 并且是名词;

  • 变量名: UpperCamelCase, 应该是名词,因为表示的是状态;

  • 函数名: lowerCamelCase, 应该是动词短语;

  • Enum declarations (e.g. enum Foo {...}) are types, 同类型也是 UpperCamelCase;

  • Enumerators (e.g. enum { Foo, Bar }) and public member 也是 UpperCamelCase;

备注

  • LLVM对于变量的命名是 UpperCamelCase, 这个我不太习惯,还是倾向于谷歌的 snake_case, 或者有时候个人也倾向 lowerCamelCase, QT的也是 lowerCamelCase

  • LLVM对函数的命名是 lowerCamelCase, 这个QT也是 lowerCamelCase, 但是谷歌、虚幻引擎的是 UpperCamelCase, C社区都是 snake_case, 我经常混用 :)

这个是QT的风格说明: https://wiki.qt.io/Qt_Coding_Style

C Coding Standards

风格如下:

  • 变量名: 基本一律 snake_case

  • 函数名: 基本一律 snake_case

  • 类型名:
    • QEMU 的结构体命名是 UpperCamelCase

    • linux 的结构体命名是 snake_case, 如果用了 typedef 会用 _t 后缀

使用clang-format

一致性最重要,过时的匈牙利前缀命名法要避免。其他的都是个人习惯,新项目达成内部团队一致。个人倾向于谷歌的风格。

使用 clang-format 十分方便,而且 clang-format 内置了 Google Style,可以添加下面的头文件:

https://github.com/google/googletest/blob/main/.clang-format

内容:

# Run manually to reformat a file:
# clang-format -i --style=file <file>
Language:        Cpp
BasedOnStyle:  Google

查看详细内容可以:

clang-format --style=Google --dump-config

在项目的根目录下放一个 clang-format 文件即可,也可以基于这个少量定制,比如适当的放宽行宽放到 100 等。可用下面的定制:

Language:        Cpp
BasedOnStyle:  Google

# 最大行宽 100 (谷歌默认80)
ColumnLimit:     100

# 函数后面的 { 换行 (谷歌默认不换行)
BreakBeforeBraces: Custom
BraceWrapping:
  AfterFunction: true

# 缩进4个空格 (谷歌默认2个空格)
IndentWidth: 4
# public/private/protected 缩进2个,把上面缩进2个,如果-4就是不缩进(谷歌默认缩进1个)
AccessModifierOffset: -2

上面就是自定义少量修改的 style 了。