← 返回首页

C++ 技术栈:从编译器到上线

· 约 8 分钟

很多人一提到 C++ 技术栈,想到的是「一个编译器加一个 IDE」。但真正在项目里干活时,编译器只占很小一环:代码怎么写进去、错误怎么查出来、依赖怎么锁住、性能问题怎么定位,这些才决定了你每天是愉快还是痛苦。

这篇文章把我日常在用的那一套整理出来。原则只有两条:换一台机器也能一条命令构建,出问题能在十分钟内定位。

一、编译器:选一个,但要知道另外两个的脾气

主流的就三家:GCC、Clang、MSVC。我的默认是 Clang,原因是诊断信息可读性好、对 sanitizer 支持最全;GCC 在 Linux 服务器上生态最稳,很多发行版默认;MSVC 则是 Windows 上唯一的选择,而且它对标准库和 ODR 的严格程度经常能提前暴露跨平台问题。

语言标准按项目明确写死,别指望默认值:

-std=c++20   # 或 c++23;显式写,别用 -std=gnu++17 这种隐式扩张
-Wall -Wextra -Wpedantic
-Wconversion -Wshadow        # 团队能忍就加,能省掉很多隐式转换 bug

如果项目要跨平台,CI 上至少跑两个编译器。只在 Clang 上通过的代码,换 GCC 常常会报「某个名字在某个模板实例化点不可见」——这不是编译器挑剔,是代码确实不规范。

二、构建:CMake + Ninja,别再手写编译器命令

CMake 有它的历史包袱,但它是事实标准,第三方库的集成成本最低。关键是用 target-based 写法,把编译器选项、include 目录、宏都挂在 target 上,而不是全局 include_directories():

cmake_minimum_required(VERSION 3.25)
project(hello LANGUAGES CXX)

add_executable(hello src/main.cpp)
target_compile_features(hello PRIVATE cxx_std_20)
target_compile_options(hello PRIVATE -Wall -Wextra -Wpedantic)

# 依赖只跟着这个 target 走,别的 target 不会被动继承
find_package(fmt REQUIRED)
target_link_libraries(hello PRIVATE fmt::fmt)

生成器用 Ninja,增量构建比 Make 快一个档次。再配 CMakePresets.json,本地和 CI 用同一份配置,就不会出现「我这儿能编」的经典事故。重复构建多的话加 ccache,改一个文件后的重编时间能从十几秒降到一秒以内。

三、依赖管理:三条路线,按项目规模挑

我的习惯是内部小库走 FetchContent,对外发布的产品用 vcpkg manifest 模式,把依赖版本全部写进 vcpkg.json 并提交到仓库。

四、调试与诊断:先让程序自己招供

遇到崩溃,第一反应不该是加 printf,而是让 sanitizer 说话。Debug 构建和 CI 里常开:

交互式调试用 lldb 或 gdb。把 ptype、frame variable 和条件断点用熟,比在任何 IDE 里点来点去都快。挂死在线上又没法重编时,valgrind 的 memcheck 依然是最省事的兜底——代价是慢 20 倍以上。

五、性能:先测量,再优化

没有测量就没有优化。工作流一般是:perf record 采一段时间 → perf report 看热点 → 只改那一个函数 → 用基准测试确认真的变快了。

微基准用 Google Benchmark,别自己拿 chrono 手搓循环——编译器会把你的循环优化掉,或者因为缓存预热给出过于乐观的数字。想确认某段代码到底生成了什么指令,扔进 Compiler Explorer 看汇编,比对着源码猜有效得多。

发布构建用 -O2。别用 -march=native:它会让二进制只在你自己的 CPU 上跑得快,在别人的机器上直接 Illegal instruction。

六、代码质量:让机器管格式,让人管设计

clang-format 定格式,clang-tidy 定规则,两者都接进 pre-commit。格式争论是纯浪费,把它交给工具之后,代码评审就只剩下真正值得讨论的东西:接口是否合理、错误怎么传播、所有权归谁。

七、我的一份最小可用组合

不想纠结的话,直接抄这一份:

这套东西没有一样是新奇的,全部是过去十年里反复被验证过的默认选项。技术栈的价值不在于「全」或者「潮」,而在于稳定到你可以不去想它——需要的时候它在,不需要的时候它不打扰你。