跳转到正文
zeno's blog
返回

C++ 工程化(二):命名规范没有统一标准但有底线规则

专题: C++ 工程化

Table of contents

Open Table of contents

TL;DR

C++ 没有统一的命名规范,主流风格(STL/Google/LLVM/Qt/Unreal)各自为政——这是 C++ 40 年多时代叠加的历史遗留问题。但”没有统一标准”不等于”怎么写都行”:C++ 标准对保留标识符有硬性规定,违反就是 UB;宏必须全大写也是跨风格的唯一共识。实操铁律是:进入已有 codebase 严格 follow,新建项目选一个成熟 style guide 一以贯之,并用 clang-format 强制执行——风格选择的质量远不如风格一致性的质量重要


1. 为什么 C++ 不像 Go/Rust 那样统一?

这是理解整个 C++ 命名生态的前提。

1.1 其他现代语言怎么做到统一的

1.2 C++ 的历史包袱

结果就是:每个大组织/大项目搞自己的一套,还都能找到”合理”的理由——这不是一个可以靠投票解决的问题,而是一个可以靠 clang-format 逐项目解决的问题


2. 主流风格全景对比

风格类型函数/方法变量成员变量常量命名空间代表项目
STL / Boostsnake_casesnake_casesnake_casem_foofoo_snake_casesnake_casestd::boost::
GooglePascalCasePascalCase(方法) / snake_case(访问器)snake_casefoo_kMaxSizesnake_caseChromium、TensorFlow、gRPC、Protobuf
LLVMPascalCasecamelCasePascalCase无前缀PascalCasesnake_case/lowercaseLLVM、Clang、Swift 编译器
QtQPascalCasecamelCasecamelCasem_memberQt、KDE
UnrealFFoo(struct)/AFoo(actor)/UFoo(UObject)/IFoo(interface)PascalCasePascalCasePascalCaseUE 游戏代码
Microsoft (现代)PascalCasePascalCasecamelCasem_fooPascalCasePascalCaseWindows SDK、.NET/C++

读法:横向对比同一项(比如”变量”)在不同风格下的写法,纵向对比同一风格的整体协调性。没有哪一种是”对的”,但某些组合在视觉上更协调。


3. 各风格的历史源流(理解”为什么”)

3.1 STL / Boost:snake_case 的思想源自 C 和 Unix

Stepanov 1994 年为 HP 设计 STL 时刻意选择 snake_case

Boost 1998 年成立时明确声明”模仿 STL 风格”,为后来被标准吸收做准备(std::shared_ptrstd::threadstd::filesystem 全部来自 Boost)。

推论:如果你写的库打算进入 std:: 或跟标准库配合使用,应该用 snake_case——这是”礼貌”。

3.2 Google:PascalCase 来自 Java 背景

Google 早期 C++ style guide(2008 年前后公开)的作者很多来自 Java 背景,自然带入了 Java 的 PascalCase 方法名:

getter/setter 走 snake_case 的特例是 Google 后来(~2010 年)为了”让 property 访问看起来像字段访问”而引入的——争议很大,很多新人反复踩坑。

推论:Google 风格适合业务代码密集、团队规模大、需要明确视觉区分类型/函数/变量的场景。

3.3 LLVM:极简主义的 PascalCase 变量

Chris Lattner 2003 年启动 LLVM 时的选择:

代价Foo bar = Foo(); 这种语句视觉上类型和变量都是首字母大写,初读容易混淆——但 LLVM 社区觉得这是”熟练度问题,不是风格问题”。

推论:LLVM 风格适合代码量大、人均熟练度高、愿意靠工具(clang-tidy)辅助阅读的场景。

3.4 Qt:受 Java/Smalltalk 影响的 camelCase

Qt 1995 年诞生时,Trolltech 团队受 Smalltalk 和 Java 影响:

历史遗产:即使今天 Qt 有 QtCore namespace,Q 前缀仍然保留——因为改名会破坏整个 KDE 生态。

3.5 Unreal Engine:匈牙利命名法的现代变种

Epic 的 UE 代码(Unreal Script 时代继承下来):

推论:这是游戏引擎的特殊需求——UE 的反射系统、GC、蓝图都依赖前缀做编译期/运行期类型判别。不要在普通 C++ 项目学这个。

3.6 Microsoft:匈牙利命名法的退场

1990 年代 Microsoft 文档里充斥着 szName(sz = string, zero-terminated)、lpszBuffer(lp = long pointer)、hWnd(h = handle)这种系统匈牙利命名法。

2000 年后 Microsoft 逐渐抛弃这套,现代 Windows/.NET C++ 代码用 PascalCase 类型 + camelCase 变量,只有在维护老 Win32 API 时还能看到匈牙利命名。

推论:看到 szNamelpBuffer 就知道是老代码,不要在新代码写这种。


4. 典型风格对比同一段代码

4.1 STL 风格(snake_case 一路到底)

namespace my_lib {

class thread_pool {
public:
    using task_type = std::function<void()>;

    explicit thread_pool(size_t num_workers);
    ~thread_pool();

    void submit_task(task_type task);
    size_t active_workers() const noexcept;
    bool is_stopped() const noexcept { return stopped_; }

private:
    std::vector<std::thread> workers_;
    std::queue<task_type> pending_tasks_;
    std::atomic<bool> stopped_;
    mutable std::mutex queue_mutex_;
};

}  // namespace my_lib

特点

Why:阅读这段代码的人正在 include <vector><thread>,视觉连贯性降低认知负载。

4.2 Google 风格

namespace my_lib {

class ThreadPool {
public:
    using TaskType = std::function<void()>;  // 类型别名:PascalCase

    explicit ThreadPool(size_t num_workers);
    ~ThreadPool();

    void SubmitTask(TaskType task);                           // 方法:PascalCase
    size_t active_workers() const { return active_workers_; } // 访问器例外:snake_case
    bool is_stopped() const { return stopped_; }              // 布尔访问器也用 snake_case

    static constexpr int kMaxWorkers = 1024;  // 常量:k 前缀

private:
    std::vector<std::thread> workers_;        // 成员:尾部下划线
    std::atomic<int> active_workers_;
    std::atomic<bool> stopped_;
};

}  // namespace my_lib

特点

Why:类型/方法都是 PascalCase 形成”主动操作”视觉,访问器用 snake_case 形成”被动读取字段”视觉——理论上让读者一眼区分”这是操作”vs”这是取值”。

4.3 LLVM 风格

namespace my_lib {

class ThreadPool {
public:
    using TaskType = std::function<void()>;

    explicit ThreadPool(size_t NumWorkers);  // 参数也是 PascalCase
    ~ThreadPool();

    void submitTask(TaskType Task);          // 方法:camelCase
    size_t getActiveWorkers() const;
    bool isStopped() const { return Stopped; }

    static constexpr int MaxWorkers = 1024;  // 常量也是 PascalCase,无前缀

private:
    std::vector<std::thread> Workers;        // 变量:PascalCase,无前缀
    std::atomic<int> ActiveWorkers;
    std::atomic<bool> Stopped;
};

}  // namespace my_lib

特点

Why:极端节省视觉噪声,相信程序员的熟练度——“看代码不需要靠前缀辅助”。

4.4 Qt 风格

namespace my_lib {

class ThreadPool : public QObject {
    Q_OBJECT
public:
    explicit ThreadPool(int numWorkers, QObject *parent = nullptr);
    ~ThreadPool() override;

    void submitTask(std::function<void()> task);
    int activeWorkers() const;
    bool isStopped() const { return m_stopped; }

signals:
    void taskCompleted(int taskId);   // signal:动词过去式
    void workerStarted(int workerId);

private slots:
    void onWorkerFinished();           // slot:on + 事件

private:
    QVector<QThread*> m_workers;       // 成员:m_ 前缀
    QAtomicInt m_activeWorkers;
    bool m_stopped;
};

}  // namespace my_lib

特点


5. 深入专题:C++ 特有的命名维度

5.1 命名空间

// STL 风格:全小写 + snake_case(标准库做法)
namespace std::chrono { }
namespace std::filesystem { }

// Google/大部分项目:全小写 + snake_case
namespace my_company::backend::rpc { }

// LLVM:全小写(但有时嵌套深时用单词缩写)
namespace llvm::cl { }  // cl = command line
namespace llvm::sys { } // sys = system

// 反模式:namespace 用 PascalCase
namespace MyCompany::Backend { }  // 极少见,不推荐

共识:命名空间几乎所有风格都推荐 snake_case,因为它们经常嵌套成 a::b::c,大小写混用会视觉混乱。

5.2 文件命名

文件名有三个维度:大小写分隔符扩展名

风格头文件源文件示例
STL / Boost.hpp 或无扩展.cppshared_ptr.hpp
Google.h.ccthread_pool.hthread_pool.cc
LLVM.h.cppThreadPool.hThreadPool.cpp
Microsoft / Qt.h.cpp类名一致

几个反模式

Google 风格的文件映射规则ThreadPool 类 → thread_pool.h + thread_pool.cc,一一对应,方便工具查找。

5.3 enum 和 enum class

C++11 之前的 enum 污染外部作用域,所以老代码有大量前缀防撞:

// C++03:前缀是刚需
enum Color {
    COLOR_RED,
    COLOR_GREEN,
    COLOR_BLUE,
};
// 必须 COLOR_RED 否则和全局的 RED 撞

// Windows API 的典型反模式(为了防撞而巨丑)
enum WINDOW_STYLE {
    WS_BORDER = 0x00800000,
    WS_CAPTION = 0x00C00000,
};

C++11 引入 enum class 后,枚举值有自己的作用域,前缀变成多余:

// C++11+:enum class 无需前缀
enum class Color {
    Red,        // 访问时 Color::Red,天然防撞
    Green,
    Blue,
};

// Google 风格坚持 k 前缀
enum class Color {
    kRed,
    kGreen,
    kBlue,
};

// STL 风格用 snake_case
enum class memory_order {
    relaxed,
    consume,
    acquire,
    release,
    acq_rel,
    seq_cst,
};

建议

5.4 模板参数

STL 约定俗成的单字母大写:

template <typename T>                      // 单类型:T
template <typename T, typename U>          // 两个:T, U
template <typename K, typename V>          // K-V 对用 K, V
template <typename Iter>                   // 迭代器用 Iter 或 It
template <typename InputIt, typename OutputIt>  // 有语义区分时写全
template <typename... Args>                // 变参包用 Args(或 Ts)
template <std::size_t N>                   // 非类型模板参数用单大写字母
template <template <typename> class Alloc> // 模板模板参数

Google 风格更倾向描述性名字:

template <typename ValueType, typename HashFunction>
class HashMap { };

争议点

5.5 Concept(C++20)

C++20 引入 concept 后出现新的命名争议:

// STL 标准库选择:snake_case
template <typename T>
concept integral = std::is_integral_v<T>;

template <typename I>
concept input_iterator = requires(I i) { *i; ++i; };

// Google/LLVM 倾向 PascalCase
template <typename T>
concept Integral = std::is_integral_v<T>;

template <typename T>
concept Hashable = requires(T a) {
    { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};

标准库选择 snake_case 的理由:concept 在类型位置使用(Integral auto x),它”约束了类型”但本身”像一个类型谓词”,跟 is_integral_v 这种 type trait 性质接近。

实操建议:跟随你项目的类型命名风格——用 PascalCase 命名类型就用 PascalCase 命名 concept,用 snake_case 就用 snake_case。标准库的 snake_case 选择在 Google 风格项目里会显得突兀。

5.6 类型别名(using / typedef

// STL:snake_case,常见后缀 _type、_t
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using iterator = /* ... */;

// 自定义后缀 _t 模仿 C 标准
using buffer_t = std::vector<uint8_t>;

// Google:PascalCase
using UserId = int64_t;
using CallbackFunc = std::function<void(int)>;

// LLVM:PascalCase
using SymbolMap = DenseMap<StringRef, Symbol*>;

注意:不要同时使用 _type_t 两种后缀在同一个类里,这是常见的”风格漂移”问题。

5.7 布尔变量和函数

所有风格都推荐以动词/系动词开头,让读者读成一句话:

bool is_ready;       // "是否就绪"
bool has_error;      // "是否有错"
bool should_retry;   // "是否应重试"
bool can_modify;     // "能否修改"
bool was_cancelled;  // "是否已取消"

// 反模式:名词形式的布尔
bool ready;          // 不清楚是"准备好了"还是"准备好"这个动作
bool error;          // 看起来像错误对象,不像布尔
bool cancel;         // 动词,看起来像命令不像状态

Google 特别规定:不要用否定式布尔命名。

// 反模式:否定式
bool is_not_ready;
if (!is_not_ready) { }  // 双重否定,读起来头疼

// 正确:用肯定式
bool is_ready;
if (!is_ready) { }

5.8 Getter / Setter 的三种流派

// ---- Java 风格(Google 方法层用这个)----
class User {
public:
    const std::string& GetName() const { return name_; }
    void SetName(const std::string& name) { name_ = name; }
private:
    std::string name_;
};

// ---- 无前缀风格(Google 访问器 + STL 的做法)----
class User {
public:
    const std::string& name() const { return name_; }
    void set_name(const std::string& name) { name_ = name; }
    // 注意:getter 无前缀,setter 有 set_ 前缀——不对称但是主流
private:
    std::string name_;
};

// ---- LLVM / Modern C++ 风格 ----
class User {
public:
    StringRef getName() const { return Name; }
    void setName(StringRef N) { Name = N; }
private:
    std::string Name;
};

现代共识:对小类型(intboolStringRef)用”无前缀 getter + set_ setter”最简洁。大型成员(返回 const T&)也用同样模式。只有强 Java 背景的团队保留 Get/Set 全套前缀。

5.9 缩写和全大写单词的处理

经典难题:HTTPServer 还是 HttpServerIO 还是 Io

// ---- 保留原始大小写("HTTP is an acronym so it's all caps")----
class HTTPServer { };
class XMLParser { };
int HTTPStatusCode();
class URLBuilder { };

// ---- 全部作为"一个单词"处理(Google 推荐)----
class HttpServer { };
class XmlParser { };
int HttpStatusCode();
class UrlBuilder { };

Google 的理由HTTPServerHTTPSServer 紧邻时视觉很难区分(HTTPSServerHTTPSServer 的边界不明显),而 HttpsServer vs HttpServer 一眼可辨。

LLVM 不作强制要求,但 LLVM 源码里 JITIRAST 都保留大写,只有更长的缩写(HttpClient)才小写。

推论:选一条严格执行——HTTPServer + JITCompilerHttpServer + JitCompiler 都 OK,但不要混用 HTTPServer + JitCompiler

5.10 接口/抽象类是否加 I 前缀

// ---- Microsoft/Unreal/Java 风格 ----
class IRenderer {
public:
    virtual ~IRenderer() = default;
    virtual void render() = 0;
};
class OpenGLRenderer : public IRenderer { };

// ---- 无前缀风格(Google、STL、Modern C++ 主流)----
class Renderer {
public:
    virtual ~Renderer() = default;
    virtual void Render() = 0;
};
class OpenGLRenderer : public Renderer { };

反对 I 前缀的理由(Google/C++ Core Guidelines 主张):

支持 I 前缀的理由(Microsoft/Unreal 主张):

推论:Modern C++ 主流去前缀;只有当你的项目是 COM/UE 生态的一部分时才加 I

5.11 异常类命名

// STL 风格:错误类型后缀 _error(继承自 std::exception 体系)
class runtime_error;
class logic_error;
class out_of_range;

// 自定义遵循同样模式
class connection_error : public std::runtime_error { };

// Google/LLVM 风格:Exception 或 Error 后缀
class ParseError : public std::exception { };
class ConnectionException : public std::runtime_error { };

建议:跟随基类风格——如果继承 std::runtime_error(snake_case),自定义也用 snake_case + _error 后缀,让整个错误体系视觉一致。

5.12 物理单位的命名

C++ 没有 Rust/Haskell 的强类型 unit 系统,命名里写清单位是防止 bug 的重要手段:

// 反模式:单位不明
int timeout;           // 毫秒?秒?微秒?
double distance;       // 米?英尺?
int buffer_size;       // 字节?KB?

// 推荐:单位作为后缀
int timeout_ms;        // 明确毫秒
int timeout_us;        // 微秒
double distance_m;     // 米
int buffer_size_bytes;
int64_t file_size_kb;

C++20 <chrono> 的解决方案:用类型承载单位,彻底消除命名里写单位的需要:

using namespace std::chrono_literals;
std::chrono::milliseconds timeout = 500ms;
std::chrono::microseconds precise_delay = 100us;
// 类型系统强制你不能把 ms 当 us 用

6. 常见误区(Pitfalls)

6.1 保留标识符是硬性规则,不是风格问题「确定,来源:C++ 标准 [lex.name]/3」

C++ 标准规定以下名字保留给实现,用户代码定义它们是 UB(大多数编译器不会报错,但标准库头文件可能用到这些名字,触发诡异冲突或平台相关 bug):

模式作用域是否 UB例子
_Name(下划线 + 大写字母)任何作用域UB_Foo_X_MyVar
__name(任何位置含双下划线)任何作用域UBmy__name__initfoo__bar
_name(下划线 + 小写)全局命名空间UBglobal 作用域的 _foo
_nameclass/函数作用域OK 但危险class 成员 _member 合法
// UB - 保留名字
int _MyVar = 0;          // _Uppercase 永远保留
int __counter = 0;       // 双下划线永远保留
namespace { int _foo; }  // 在 global namespace 严格说 UB
#define __DEBUG 1         // 双下划线宏更危险,必然撞库

// OK
class Foo {
    int _m;              // class 作用域,_ + 小写,合法但有歧义风险
    int m_;              // 尾部下划线,安全
    int m_m;             // m_ 前缀,安全
};
int my_var_ = 0;         // namespace 作用域,尾部下划线 OK

为什么这条规则如此重要:标准库实现者(libstdc++、libc++、MSVC STL)大量使用 _Name__name 作为内部名字——你写 _Size 某天编译器升级就会撞名。这是定时炸弹级别的 bug,很难排查。

6.2 成员变量前缀:m_ vs 前导 _ vs 尾部 _

前缀方式安全性视觉强度典型使用者
m_foo绝对安全Qt、Unreal、老派 C++
foo_(尾部)安全Google、Boost
_foo(前导)有风险新手常用,不推荐
无前缀(foo安全LLVM

前导下划线的陷阱

class Foo {
    int _size;   // class 作用域,技术上合法
};

// 某天代码重构把 _size 提取成 helper 函数:
int _size() { /* ... */ }  // 全局作用域,UB!
// 编译通过,行为未定义

建议:用 m_ 或尾部 _ 二选一,永远不要前导下划线。即使在 class 作用域技术上合法,跨文件重构时容易漂移到 UB 区域。

6.3 宏必须 UPPER_SNAKE_CASE——这是唯一近乎普世的规则

#define MAX_BUFFER_SIZE 4096   // 正确:视觉警告"这是宏"
#define maxBufferSize 4096     // 错误:看起来像变量
#define max_buffer_size 4096   // 错误:看起来像函数

// 灾难场景:宏名和标识符冲突
#define max 1024
#include <algorithm>
int x = std::max(a, b);  // 展开成 std::1024(a, b),编译失败或更糟

Why:宏绕过名字查找、作用域、命名空间,全大写是唯一的视觉警告。违反这条就是给未来的自己埋雷。

附加规则:宏名应该带项目前缀防撞,尤其是 header-only 库:

// 反模式:通用名字
#define CHECK(x) if (!(x)) abort()
#define LOG(msg) std::cerr << msg

// 正确:带项目前缀
#define MYLIB_CHECK(x) if (!(x)) abort()
#define MYLIB_LOG(msg) std::cerr << msg

// header guard 也一样
#ifndef MYLIB_FOO_H_
#define MYLIB_FOO_H_
// ...
#endif  // MYLIB_FOO_H_

6.4 常量的 k 前缀有争议

// Google 坚持
static constexpr int kMaxSize = 1024;
static constexpr double kPi = 3.14159;

// LLVM / Modern C++
static constexpr int MaxSize = 1024;
static constexpr double Pi = 3.14159;

// STL
inline constexpr int max_size = 1024;

反对 k 前缀的理由

支持 k 前缀的理由

推论:这是一条可以跳过的 Google 式教条,除非你的项目其他部分都是 Google 风格(保持一致性 > 个人偏好)。

6.5 匈牙利命名法已经死了(别学)

// 系统匈牙利命名(Windows API 遗产)
int iCount;
char* szName;
DWORD dwFlags;
LPVOID lpBuffer;
HWND hWnd;

// Apps 匈牙利命名(原始意图,较少见)
int cx;  // count of x
int dx;  // delta x
char* pszBuffer;  // pointer to string, zero-terminated

为什么淘汰

唯一的例外:维护老 Win32 代码时跟随原有风格,不要在新代码学这个。

6.6 snake_casePascalCase 在同一 class 里混用是最糟的选择

// 反模式:风格漂移
class ThreadPool {
public:
    void submit_task(Task t);     // STL 风格
    void SubmitTask(Task t);      // Google 风格
    void submitTask(Task t);      // LLVM 风格
    // 三种风格同时存在,通常是不同人分别添加的
};

典型出现场景

补救:在 CI 里加 clang-tidyreadability-identifier-naming 检查,强制全项目一致。

6.7 auto 让变量命名更重要

auto 隐藏类型,所以变量名必须承担更多语义:

// 反模式:auto + 无信息变量名
auto x = getData();
auto y = process(x);
auto z = format(y);
// 读者根本不知道 x/y/z 是什么类型、什么含义

// 正确:auto + 描述性名字
auto user_records = fetch_user_records(db);
auto filtered_records = filter_active(user_records);
auto json_output = to_json(filtered_records);
// 即使看不到类型也能推断用途

C++ Core Guidelines ES.11:使用 auto 时,命名必须补足类型隐藏带来的信息损失。

6.8 lambda 捕获命名

// 反模式:捕获名和外层同名,引发阴影
int count = 0;
auto lambda = [count]() {
    int count = 10;  // 编译警告:shadow outer capture
    return count;
};

// 正确:需要修改时用不同名字
int count = 0;
auto lambda = [captured_count = count]() {
    return captured_count * 2;
};

C++14 初始化捕获让捕获变量有了独立命名的空间,可以显式区分”外层变量”和”lambda 内变量”。


7. clang-format 实战

风格选择的 80% 问题可以靠 .clang-format 文件解决。以下是三种风格的起手模板。

7.1 Google 风格(最常用,起手推荐)

# .clang-format
BasedOnStyle: Google
IndentWidth: 2
ColumnLimit: 100
AccessModifierOffset: -1
DerivePointerAlignment: false
PointerAlignment: Left
AllowShortFunctionsOnASingleLine: Inline
IncludeBlocks: Regroup

7.2 LLVM 风格

BasedOnStyle: LLVM
IndentWidth: 2
ColumnLimit: 80
AlignConsecutiveDeclarations: false
PointerAlignment: Left

7.3 STL / Boost 风格(custom)

BasedOnStyle: LLVM
IndentWidth: 4
ColumnLimit: 120
BreakBeforeBraces: Allman
AlignConsecutiveDeclarations: true
# 注意:clang-format 不能强制 snake_case 命名
# 命名检查需要配合 clang-tidy

7.4 clang-tidy 命名检查

clang-format 只管缩进和空白,命名风格要靠 clang-tidy

# .clang-tidy
Checks: "readability-identifier-naming"
CheckOptions:
  - key: readability-identifier-naming.ClassCase
    value: CamelCase
  - key: readability-identifier-naming.FunctionCase
    value: CamelCase
  - key: readability-identifier-naming.VariableCase
    value: lower_case
  - key: readability-identifier-naming.PrivateMemberSuffix
    value: "_"
  - key: readability-identifier-naming.ConstantCase
    value: CamelCase
  - key: readability-identifier-naming.ConstantPrefix
    value: "k"
  - key: readability-identifier-naming.MacroDefinitionCase
    value: UPPER_CASE
  - key: readability-identifier-naming.EnumConstantCase
    value: CamelCase
  - key: readability-identifier-naming.EnumConstantPrefix
    value: "k"

集成到 CI

# pre-commit hook
clang-format -i --style=file $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cc|cpp|h|hpp)$')
clang-tidy --config-file=.clang-tidy $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cc|cpp)$')

关键洞察:一旦 .clang-format + .clang-tidy 就位,风格选择就不再是持续消耗认知的事情——工具保证一致性,人类只在最初选一次。


8. 决策框架:新项目怎么选?

是否有大量 STL/Boost 集成或打算贡献给标准?
├── 是 → STL 风格(snake_case),和 std:: 无缝
└── 否
    ├── 是否是编译器/语言基础设施(LLVM 生态)?
    │   ├── 是 → LLVM 风格
    │   └── 否
    │       ├── 是否是游戏引擎(UE 生态)?
    │       │   ├── 是 → Unreal 风格(F/A/U 前缀)
    │       │   └── 否
    │       │       ├── 是否是 Qt/KDE 应用?
    │       │       │   ├── 是 → Qt 风格
    │       │       │   └── 否 → Google 风格(默认推荐)

为什么 Google 风格是”默认”

Google 风格的缺点

如果预算允许只关心一件事:在项目第一天就写好 .clang-format,并在 CI 里强制执行——风格本身的选择远不如”有没有强制一致性”重要。


9. C++ Core Guidelines 的底线

Stroustrup & Sutter 的 C++ Core Guidelines NL 章节(Naming and Layout)是”最低共识”,任何风格都应该满足:

NL.7 的”作用域比例”原则特别值得一提:

for (int i = 0; i < n; ++i) { }  // 局部循环变量 i 合理
int i = globalInterfaceCounter;  // 全局作用域用 i 不合理

// 作用域越大,名字越描述性
class Foo {
    int total_user_count_;  // 类成员作用域
    void update(int n) {    // 函数参数作用域小,短名可以
        for (int i = 0; i < n; ++i) { }
    }
};

10. 实操总结

  1. 进入已有项目:严格跟随项目风格,不要混搭。混搭是最糟的选择——比任何一种风格都难读
  2. 新项目选型:按第 8 节决策框架走,默认选 Google
  3. 必装 .clang-format + .clang-tidy:写进仓库根目录,pre-commit hook 自动 format + 检查命名
  4. 硬规则不能违反:保留标识符(6.1)、宏全大写(6.3)、不要匈牙利命名(6.5)——这三条跨风格通用
  5. 命名承担语义auto 时代变量名要承担类型隐藏带来的信息损失
  6. 风格一致性 > 风格选择:选哪种不重要,全项目一致才重要

一句话总结:C++ 命名的真正最佳实践不是”选对风格”,而是”选一种并坚持到底,用工具替代人类的意志力”。


信息来源

关联概念


分享这篇文章:

上一篇
Go 数据库:sqlc 的 SQL-first 类型安全方案
下一篇
Go RPC:gRPC、HTTP/2 与 proto 契约