CANN hixl 仓库 C++ 代码风格规范实战指南:命名、格式与注释规范全解析
【免费下载链接】hixlHIXL(Huawei Xfer Library)是一个灵活、高效的昇腾单边通信库,面向集群场景提供简单、可靠、高效的点对点数据传输能力。项目地址: https://gitcode.com/cann/hixl
本文以 docs/zh/contributions/coding_standards/cpp-style.md 为骨架,系统讲解 CANN 开源社区(含 hixl 仓库)的 C++ 代码风格规范。全文覆盖命名、格式、注释三大维度的 20 余条规则与建议,并结合仓库根目录 .clang-format 配置与 src/hixl 目录下的真实源码进行印证。读者完成本文后可对照规范自查代码、理解 clang-format 与风格规则的对应关系,并能顺利通过 docs/zh/contributions/precommit_guide.md 描述的 pre-commit 代码格式化与合规检查。
规范背景与适用范围
CANN 的 C++ 代码风格规范以 Google C++ Style Guide 为基础,参考 MindSpore 社区与华为通用编码规范,并结合业界共识整理而成。参与 CANN 开源社区项目(包括本 hixl 仓库)的开发者,首先需要遵循本规范内容,其余场景遵循 Google C++ Style Guide。若对规则有异议,可提交 issue 说明理由,经 CANN 运营团队评审通过后可接纳并修改生效。
规范的适用对象是CANN 相关开源仓的代码风格检视,即所有新提交的 C++ 代码都应满足下表所列的规则。规范按「规则(Rule)」与「建议(Suggestion)」区分强制力:规则类条目(如 1.1、2.2、2.4)属于必须遵守的硬性要求;建议类条目(如 2.1、3.4、3.5)属于推荐实践,优先级略低但同样应当遵循。
规范总览
| 规范编号 | 规范名称 | 类别 | 严重级别 |
|---|---|---|---|
| 1.1 | C++文件使用小写+下划线命名 | 命名 | 中 |
| 1.2 | 函数命名使用大驼峰风格 | 命名 | 中 |
| 1.3 | 类型命名采用大驼峰风格 | 命名 | 中 |
| 1.4 | 变量命名采用小写下划线(snake_case)风格 | 命名 | 中 |
| 1.5 | 宏、枚举值采用全大写下划线连接 | 命名 | 中 |
| 1.6 | 编译期常量采用 k 前缀大驼峰 | 命名 | 中 |
| 2.1 | 行宽不超过 120 个字符 | 格式 | 低 |
| 2.2 | 使用空格缩进,每次 2 个空格 | 格式 | 中 |
| 2.3 | &、*跟随变量名 | 格式 | 低 |
| 2.4 | if 语句必须使用大括号 | 格式 | 中 |
| 2.5 | for/while 循环必须使用大括号 | 格式 | 中 |
| 2.6 | 表达式换行运算符放行末 | 格式 | 低 |
| 2.7 | 使用附着式(Attach)大括号风格 | 格式 | 中 |
| 2.8 | 多个变量定义不允许写在一行 | 格式 | 低 |
| 2.9 | 合理安排空行,保持代码紧凑 | 格式 | 低 |
| 3.1 | 文件头注释包含版权声明 | 注释 | 中 |
| 3.2 | 右置注释使用//,注释与代码间有空格 | 注释 | 低 |
| 3.3 | 禁止使用 TODO/TBD/FIXME 注释 | 注释 | 中 |
| 3.4 | 不要写空有格式的函数头注释 | 注释 | 低 |
| 3.5 | 不用的代码直接删除,不要注释掉 | 注释 | 中 |
1. 命名规范
命名风格基础
规范中涉及两种基本命名风格:
- 驼峰风格(CamelCase):大小写字母混用,单词连在一起,不同单词间通过单词首字母大写来分开。按连接后首字母是否大写,又分为大驼峰(UpperCamelCase)和小驼峰(lowerCamelCase)。
- 小写下划线风格(snake_case):单词全部小写,单词间用下划线连接,如
table_name。
各类符号与命名风格的对应关系如下表:
| 类型 | 命名风格 |
|---|---|
| 类类型,结构体类型,枚举类型,联合体类型等类型定义,作用域名称 | 大驼峰 |
| 函数(包括全局函数,作用域函数,成员函数) | 大驼峰 |
| 编译期常量(constexpr,或以字面量/编译期常量表达式初始化的 const,含各作用域) | k 前缀大驼峰 |
| 全局变量(包括全局和命名空间域下的变量,类静态变量),局部变量,函数参数,类、结构体和联合体中的成员变量 | 小写下划线(snake_case) |
| 宏,枚举值,goto 标签 | 全大写,下划线分割 |
这里有两点需要特别注意:
- 常量的判定标准:上表中的「常量」指值在编译期即可确定的 const/constexpr 基本数据类型、枚举、字符串类型的变量(constexpr,或以字面量/编译期常量表达式初始化的 const;含各作用域),不包括数组和其他类型变量;以函数调用或运行期数据初始化的 const 变量不属于常量,按普通变量命名。
- 变量的判定标准:除常量定义以外的其他变量,均使用小写下划线(snake_case)风格。
规则 1.1 C++ 文件使用小写+下划线命名
C++ 源文件默认以.cpp结尾,头文件以.h结尾。业界还存在其他后缀表示方式:
- 头文件:
.hh、.hpp、.hxx - cpp 文件:
.cc、.cxx、.c
如果当前项目组已经使用了某种特定后缀(例如部分历史工程使用.cc),可以继续使用,但必须保持风格统一。本规范默认使用.h和.cpp作为后缀。
在 hixl 仓库中可以看到两种后缀并存但各自统一的情况:src目录下的核心实现以.cc/.h为主(如 src/hixl/cs/endpoint.cc、src/hixl/common/hixl_log.h),测试目录同样延续.cc(如 tests/cpp/hixl/common/thread_pool_ut.cc);而 include 目录对外发布的头文件统一使用.h。这正体现了「风格统一」的原则——同一仓库内不混用多种后缀。
规则 1.2 函数命名统一使用大驼峰风格
函数(全局函数、作用域函数、成员函数)统一使用大驼峰命名,一般采用动词或动宾结构:
class List { public: void AddElement(const Element &element); Element GetElement(const unsigned int index) const; bool IsEmpty() const; }; namespace Utils { void DeleteUser(); }一个重要的例外是:实现标准库、第三方库或序列化框架要求的接口契约函数(如lock/try_lock/unlock、to_json/from_json等)可保留接口要求的原名,不受本规则约束。
在 hixl 源码中,函数命名严格遵守大驼峰:如 src/hixl/cs/endpoint.cc 中的InitQueueDepth、BuildChannelName、InitChannelDesc、IsDefaultHostVaMappingEnabled等,均为「动词/动宾」或「Is 开头的布尔查询」结构。
规则 1.3 类型命名采用大驼峰命名风格
所有类型命名——类、结构体、联合体、类型别名(using/typedef)、枚举——使用相同的大驼峰约定:
// classes, structs and unions class UrlTable { ... struct UrlTableProperties { ... union Packet { ... // type aliases using PropertiesMap = std::map<std::string, UrlTableProperties *>; // enums enum UrlTableErrors { ...命名空间也建议使用大驼峰:
// namespace namespace FileUtils {}hixl 仓库中大量类型命名遵循此规则,例如 src/hixl/common/hixl_inner_types.h 中的EndpointDesc、ChannelDesc、HcommChannelDesc,以及 include/hixl/hixl_types.h 中对外暴露的CommProtocol、ChannelType等枚举类型。
规则 1.4 通用变量命名采用小写下划线(snake_case)
全局变量、函数形参、局部变量、成员变量均采用 snake_case:
std::string table_name; // Good: 推荐此风格 std::string tableName; // Bad: 禁止小驼峰风格 std::string tablename; // Bad: 禁止无单词分隔的风格 std::string path; // Good: 只有一个单词时,snake_case 为全小写全局变量应增加g_前缀,静态变量命名不需要加特殊前缀。加前缀的目的是在视觉上突出全局变量,促使开发人员对这些易出问题的变量使用更加谨慎:
- 全局静态变量命名与全局变量相同;
- 函数内的静态变量命名与普通局部变量相同;
- 类的静态成员变量和普通成员变量相同。
int g_active_connect_count; void Func() { static int packet_count = 0; ... }类的成员变量命名以 snake_case 加后下划线组成;struct 纯数据结构的公共成员可不加后下划线,同一结构内保持一致:
class Foo { private: std::string file_name_; // 类成员:添加 _ 后缀区分 }; struct Point { int x; // struct 纯数据成员:可不加后缀 int y; };hixl 源码是这一规则的标准范例:如 src/hixl/cs/endpoint.cc 中的局部变量is_client、data_depth、channel_name、client_ep、server_ep均采用 snake_case;成员变量加_后缀的模式在 src/hixl/cs/channel.h、src/hixl/fabric_mem/fabric_mem_slot_pool.h 等头文件中大量可见。
规则 1.5 宏、枚举值采用全大写,下划线连接
宏、枚举值、goto 标签使用全大写 + 下划线连接:
#define MAX(a, b) (((a) < (b)) ? (b) : (a)) // 仅对宏命名举例,并不推荐用宏实现此类功能 enum TintColor { // 注意,枚举类型名用大驼峰,其下面的取值是全大写,下划线相连 RED, DARK_RED, GREEN, LIGHT_GREEN };注意示例中的细节:枚举类型名TintColor用大驼峰,而枚举值DARK_RED用全大写 + 下划线。hixl 中的日志宏即遵循此规范,如 src/hixl/common/hixl_log.h 中的HIXL_LOGE、HIXL_LOGW、HIXL_LOGI、HIXL_LOGD、HIXL_EVENT、HIXL_RECORD,以及各类检查宏HIXL_CHK_STATUS_RET、HIXL_CHK_ACL_RET、HIXL_CHK_HCCL_RET等。
规则 1.6 编译期常量采用 k 前缀大驼峰命名
编译期常量(constexpr,或以字面量/编译期常量表达式初始化的 const)不论作用域(全局、命名空间、类静态成员、函数局部),统一使用k 前缀大驼峰命名:
int Func(...) { constexpr unsigned int kBufferSize = 100; // 编译期常量:k 前缀大驼峰 char *buffer = new char[kBufferSize]; const int saved_errno = errno; // 运行期初始化的 const:按普通变量 snake_case 命名 ... } namespace Utils { constexpr unsigned int kDefaultFileSizeKb = 200; // 全局编译期常量 }判定要点:
- 以函数调用或运行期数据初始化的 const 变量不是常量,按普通变量命名(规则 1.4);
- 类的非静态const 成员变量按成员变量命名规则(snake_case 加后下划线)命名。
hixl 源码中 k 前缀常量随处可见,例如 src/hixl/cs/endpoint.cc 中的kRoceQueueNum、kMinTransportQueueDepth,src/hixl/common/hixl_inner_types.h 中的kProtocolRoce、kProtocolUboe、kRdmaTrafficClass、kRdmaServiceLevel、kDefaultSplitBatchSize、kMaxMultiWorkerNum等。可以看到常量覆盖了数值(uint32_t、uint8_t)与字符串(const char *)两类,符合规范中「基本数据类型、枚举、字符串类型的变量」的界定。
2. 格式规范
建议 2.1 行宽不超过 120 个字符
建议每行字符数不超过120个。若超过 120 个字符,请选择合理的方式换行。允许的例外包括:
- 一行注释包含超过 120 个字符的命令或 URL,可保持一行,方便复制、粘贴和通过 grep 查找;
- 包含长路径的
#include语句可以超出 120 个字符,但应尽量避免; - 编译预处理中的 error 信息可以超出一行(保持一行便于阅读和理解)。
#ifndef XXX_YYY_ZZZ #error Header aaaa/bbbb/cccc/abc.h must only be included after xxxx/yyyy/zzzz/xyz.h, because xxxxxxxxxxxxxxxxxxxxxxxxxxxxx #endif这一限制与仓库根目录 .clang-format 中的ColumnLimit: 120完全一致,clang-format 会按此配置自动折行。
规则 2.2 使用空格进行缩进,每次缩进 2 个空格
只允许使用空格(space)缩进,每次缩进为2 个空格,不允许使用 Tab 符。当前几乎所有 IDE 都支持将 Tab 自动扩展为 2 空格输入,建议配置 IDE 开启该选项。另外,命名空间内部不缩进,与 .clang-format 的NamespaceIndentation: None一致。
对应到 .clang-format:IndentWidth: 2、TabWidth: 2、UseTab: Never。观察 src/hixl/cs/endpoint.cc 的代码,namespace hixl {内部顶格书写,函数体内容缩进 2 空格,控制流嵌套逐级加 2 空格,正是该规则的落地效果。
规则 2.3&、*跟随变量名
声明指针、引用变量或参数时,&、*跟随变量名,另外一边留空格:
char *c; const std::string &str;这与 .clang-format 的PointerAlignment: Right对应。规则 1.2 示例中的const Element &element即按此写法。
规则 2.4 if 语句必须使用大括号
即便只有一条语句,if 语句也必须使用大括号。理由如下:
- 代码逻辑直观、易读;
- 在已有条件语句代码上增加新代码时不容易出错;
- 对于在 if 语句中使用函数式宏时,有大括号保护不易出错(如果宏定义时遗漏了大括号)。
// 即使if分支代码只有一行,也必须使用大括号 if (cond) { single line code; }注意:clang-format 不会自动为单条语句补全大括号(.clang-format 中
AllowShortIfStatementsOnASingleLine: true允许单行 if 形式),本规则依赖代码检视或 clang-tidy(readability-braces-around-statements)保证。
规则 2.5 for/while 等循环语句必须使用大括号
与条件表达式类似,for/while 循环语句必须加上大括号,即便循环体是空的,或循环语句只有一条:
for (int i = 0; i < some_range; i++) { // Good: 使用了大括号 DoSomething(); }while (condition) { // Good:循环体是空,也使用大括号 }注意:同规则 2.4,clang-format 不会自动为循环语句补全大括号(.clang-format 中
AllowShortLoopsOnASingleLine: true允许单行循环形式),需依靠代码检视保证。
规则 2.6 表达式换行要保持一致性,运算符放行末
较长的表达式不满足行宽要求时,需要在适当的地方换行。一般在较低优先级运算符或连接符后面截断,运算符或连接符放在行末,表示「未结束,后续还有」:
// 假设下面第一行已经不满足行宽要求 if ((current_value > threshold) && // Good:换行后,逻辑操作符放在行尾 some_condition) { DoSomething(); ... } int result = really_long_variable_name1 + // Good really_long_variable_name2;表达式换行后,续行与首个操作数对齐(与 .clang-format 的AlignOperands: true一致):
int sum = long_variable_name1 + long_variable_name2 + long_variable_name3 + long_variable_name4 + long_variable_name5 + long_variable_name6; // Good: 续行与首个操作数对齐规则 2.7 使用附着式(Attach)大括号风格
附着式(Attach)风格:所有左大括号(包括函数、类、结构体、控制语句等)跟随语句放行末,前置 1 空格。右大括号独占一行,除非后面跟着同一语句的剩余部分,如 do 语句中的 while,或者 if 语句的 else/else if,或者逗号、分号。
struct MyType { // 跟随语句放行末,前置1空格 ... }; int Foo(int a) { // 函数左大括号同样跟随语句放行末 if (...) { ... } else { ... } }推荐这种风格的理由:
- 代码更紧凑;
- 相比另起一行,放行末使代码阅读节奏感上更连续;
- 符合后来语言的习惯,符合业界主流习惯;
- 与仓库 .clang-format 配置(
BreakBeforeBraces: Attach)一致,提交前执行 clang-format 不会产生额外 diff; - 现代 IDE 都具有代码缩进对齐显示的辅助功能,大括号放在行尾并不会对缩进和范围产生理解上的影响。
对于空函数体,可以将大括号放在同一行:
class MyClass { public: MyClass() : value_(0) {} private: int value_; };查看 src/hixl/cs/endpoint.cc 可以直观看到附着式风格的统一应用:bool IsUrmaProtocol(...)左大括号紧跟行末,if/else、for等控制结构同样如此,} else if、} else的右大括号与后续关键字同行。
规则 2.8 多个变量定义和赋值语句不允许写在一行
每行只写一个变量初始化语句,更容易阅读和理解。
规则 2.9 合理安排空行,保持代码紧凑
减少不必要的空行,可以显示更多代码,方便阅读。建议遵守以下规则:
- 根据上下内容的相关程度,合理安排空行;
- 函数内部、类型定义内部、宏内部、初始化表达式内部,不使用连续空行;
- 不使用连续空行,最多保留 1 个(与 .clang-format 的
MaxEmptyLinesToKeep: 1一致); - 大括号内的代码块行首之前和行尾之后不要加空行,但 namespace 的大括号内不作要求。
int Foo() { ... } int Bar() { // Bad:最多保留 1 个连续空行。 ... } if (...) { // Bad:大括号内的代码块行首不要加入空行 ... // Bad:大括号内的代码块行尾不要加入空行 } int Foo(...) { // Bad:函数体内行首不要加空行 ... }3. 注释规范
一般的,尽量通过清晰的架构逻辑、好的符号命名来提高代码可读性;需要的时候,才辅以注释说明。注释是为了帮助阅读者快速读懂代码,所以要从读者的角度出发,按需注释。注释内容要简洁、明了、无二义性,信息全面且不冗余。
在 C++ 代码中,使用/* */和//都是可以的。按注释的目的和位置,注释可分为不同的类型,如文件头注释、函数头注释、代码注释等;同一类型的注释应该保持统一的风格。
规则 3.1 文件头注释包含版权声明
每个源文件头部必须包含版权声明,示例如下:
/** * Copyright (c) 2026 Huawei Technologies Co., Ltd. * This program is free software, you can redistribute it and/or modify it under the terms and conditions of * CANN Open Software License Agreement Version 2.0 (the "License"). * Please refer to the License for details. You may not use this file except in compliance with the License. * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. * See LICENSE in the root of the software repository for the full text of the License. */关于版权年份的写法:
- 2026 年新建的文件,应为
Copyright (c) 2026 Huawei Technologies Co., Ltd.; - 2025 年新建、2026 年修改的文件,应为
Copyright (c) 2025-2026 Huawei Technologies Co., Ltd.。
hixl 仓库的所有源文件均带此版权头,例如 src/hixl/common/hixl_log.h 与 scripts/check_log_spec.py 的前 9 行。值得注意的是,该文件头是合规检查(OAT 扫描)的硬性要求——docs/zh/contributions/precommit_guide.md 中明确,许可证头缺失或错误会阻止提交,必须修复后才能通过 pre-commit 的oat-check。
规则 3.2 右置注释使用//,注释与代码间有空格
代码注释应置于对应代码的上方或右边;注释符与注释内容之间要有 1 个空格;右置注释与前面代码至少 1 个空格;右置注释使用//,而不是/**/;置于代码上方的注释使用//或/* */均可:
// this is multi- // line comment int foo; // this single-line comment.clang-format 中的SpacesBeforeTrailingComments: 2保证右置注释前至少保留 2 个空格。源码中该规则的典型应用如 src/hixl/common/hixl_inner_types.h 中的constexpr uint8_t kRdmaTrafficClass = 132; // RDMA网卡的traffic class 默认值。
规则 3.3 禁止使用 TODO/TBD/FIXME 注释
代码中禁止使用 TODO/TBD/FIXME 等注释,建议提 issue 跟踪待办事项。这类残留注释会在 pre-commit 流程中被扫描识别,也容易在代码评审中被拦截。
建议 3.4 不要写空有格式的函数头注释
并不是所有函数都需要函数头注释,函数尽量通过函数名自注释,按需写函数头注释;函数原型无法表达的、却又希望读者知道的信息,才需要加函数头注释辅助说明。函数头注释内容可选,但不限于:功能说明、返回值、性能约束、用法、内存约定、算法实现、可重入的要求等。
好的例子——说明了函数语义与调用者必须知道的内存约定:
/* * 返回实际写入的字节数,-1表示写入失败 * 注意,内存 buf 由调用者负责释放 */ int WriteString(const char *buf, int len);坏的例子——空有格式没内容,函数名信息冗余,关键信息缺失:
/* * 函数名:WriteString * 功能:写入字符串 * 参数: * 返回值: */ int WriteString(const char *buf, int len);上述坏例的问题在于:
- 参数、返回值空有格式没内容;
- 函数名信息冗余;
- 关键的
buf由谁释放没有说清楚。
建议 3.5 不用的代码段直接删除,不要注释掉
被注释掉的代码无法被正常维护;当企图恢复使用这段代码时,极有可能引入易被忽略的缺陷。正确的做法是:不需要的代码直接删除掉;若再需要时,考虑移植或重写这段代码。
工具链支撑:clang-format 与 pre-commit 如何保障风格落地
风格规范不只是纸面文档,仓库提供了配套工具保证落地:
.clang-format配置对照
仓库根目录 .clang-format 基于 Google 风格,将上述大部分格式规则固化为机器可执行的配置,关键项与规范的对应关系如下:
| 规范条目 | .clang-format配置项 | 配置值 |
|---|---|---|
| 建议 2.1 行宽 120 | ColumnLimit | 120 |
| 规则 2.2 缩进 2 空格 | IndentWidth/TabWidth/UseTab | 2/2/Never |
| 规则 2.2 命名空间不缩进 | NamespaceIndentation | None |
| 规则 2.3 指针引用靠右 | PointerAlignment | Right |
| 规则 2.6 续行对齐操作数 | AlignOperands | true |
| 规则 2.7 附着式大括号 | BreakBeforeBraces | Attach |
| 规则 2.9 最多保留 1 个空行 | MaxEmptyLinesToKeep | 1 |
同时,该配置也保留了与规范存在张力的选项:AllowShortIfStatementsOnASingleLine: true与AllowShortLoopsOnASingleLine: true允许单行 if/循环形式,因此规则 2.4、2.5 的「必须加花括号」无法由 clang-format 自动保证,需要依赖代码检视或 clang-tidy(readability-braces-around-statements)补充。
pre-commit 中的格式化与检查
docs/zh/contributions/precommit_guide.md 说明了 pre-commit 的完整用法:安装 pre-commit 后,每次git commit前会自动执行代码格式化(基于 clang-format 镜像)、拼写检查(codespell/typos)与 OAT 合规扫描。格式化失败时会提示修改,而真正的合规性问题(二进制文件、许可证头缺失/错误)会阻止提交。
本地也可以手动运行检查:
# 安装 pre-commit 并安装 git hooks pip install pre-commit pre-commit install # 手动运行 OAT 合规检查 pre-commit run oat-check实战自查清单
结合本文全部规则,提交 C++ 代码前可对照以下清单快速自查:
- 命名:文件名为小写+下划线(
.h/.cpp统一后缀);函数大驼峰且动词/动宾;类型(类/结构体/联合体/别名/枚举)大驼峰;普通变量 snake_case,全局变量加g_,类成员加_后缀;宏与枚举值全大写下划线;编译期常量k前缀大驼峰,运行期 const 按普通变量。 - 格式:行宽 ≤ 120;2 空格缩进且不用 Tab;
&/*跟随变量名;if/for/while 一律加大括号;表达式换行运算符在行末且续行对齐操作数;附着式大括号(空函数体可同行);一行一个变量定义;连续空行最多 1 个,代码块首尾不加空行。 - 注释:文件头带版权声明且年份正确;右置注释用
//且与代码间有空格;禁止 TODO/TBD/FIXME;函数头注释按需、有实质内容(说明语义与内存约定等);不用的代码直接删除而非注释掉。 - 工具:提交前运行 clang-format 对齐 .clang-format;依赖 pre-commit 完成格式化、拼写与 OAT 合规检查,避免二进制文件与许可证头问题阻塞提交。
hixl 仓库中 src/hixl/cs/endpoint.cc、src/hixl/common/hixl_log.h、src/hixl/common/hixl_inner_types.h 等文件是上述规范的完整落地样例,可以作为新代码的风格参考范本。
【免费下载链接】hixlHIXL(Huawei Xfer Library)是一个灵活、高效的昇腾单边通信库,面向集群场景提供简单、可靠、高效的点对点数据传输能力。项目地址: https://gitcode.com/cann/hixl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考