OpenAI 百万行代码实验解读:C++ 项目如何拥抱 AI 开发
引子
前几天看到 OpenAI 发布的一篇工程博客,标题是《Harness engineering: leveraging Codex in an agent-first world》,作者 Ryan Lopopolo 讲述了一个让人坐不住的实验:
他们用 5 个月时间,让 AI 写了 100 万行代码,构建了一个真实的产品——但工程师一行代码都没手写。
原文链接:https://openai.com/index/harness-engineering/ 发布日期:2026 年 2 月 11 日
看完这篇文章,我作为一个写了十几年 C++ 的老程序员,脑子里冒出的第一个念头是:这不可能。
第二个念头是:如果可能,那我们这些 C++ 工程师该怎么办?
虽然原文没有明确说他们用的是什么语言(很可能不是 C++),但文章里提到的那些核心理念——如何设计让 AI 能理解的架构、如何建立自动化的验证循环、如何让代码库成为知识的载体——对 C++ 项目同样适用,甚至更加重要。
因为 C++ 是出了名的复杂。内存管理、模板元编程、编译期计算、多线程并发……这些东西连人类都觉得难,AI 能搞定吗?
这篇文章是我读完 OpenAI 那篇博客后的一些思考和体会。我想从 C++ 工程师的视角,重新审视这个实验背后的理念,看看它能给我们带来什么启发。不是说要照搬他们的做法(我们的项目可能完全不同),而是思考:在 AI 参与开发的时代,什么样的工程实践才是真正有价值的?
如果你也写 C++,如果你也在思考 AI 会如何改变我们的工作,那这篇文章可能会给你一些新的视角。
一个让人坐不住的实验
OpenAI 的实验:数字背后的故事
让我先把 OpenAI 那篇文章的核心内容梳理一下:
2025 年 8 月,OpenAI 的一个小团队开始了一个疯狂的实验。他们要开发一个内部产品,但定了一条铁律:一行代码都不能手写。所有代码——应用逻辑、测试、CI 配置、文档、可观测性工具——全部由 Codex(他们的 AI Agent)生成。
五个月后,也就是 2026 年 1 月,这个产品上线了:
- 代码量:约 100 万行
- 团队规模:3 到 7 名工程师
- PR 数量:约 1500 个
- 平均产出:每人每天 3.5 个 PR
- 效率提升:估计是传统手写代码的 10 倍
产品不是玩具,而是真实可用的。有内部用户每天在用,也有外部测试者在试。它会部署、会出 bug、会被修复,就像任何一个正常的软件产品。
唯一不同的是:每一行代码都是 AI 写的。
读到这里,作为 C++ 程序员的你,可能和我有同样的反应:这怎么可能?
C++ 工程师的本能质疑
我们这些写 C++ 的人,对代码有种近乎偏执的控制欲。
内存布局要精确到字节对齐,模板展开要在编译期完成,SIMD 指令要手工优化。我们花了十几年学会如何驾驭这门语言的复杂性——从 C++98 的原始指针,到 C++11 的智能指针和移动语义,再到 C++20 的 Concepts 和 Ranges。每一次标准更新,都像是在已经很陡峭的学习曲线上又加了一道坎。
所以当看到"AI 写了 100 万行代码"这个说法时,我们的第一反应是质疑:
- 类型安全如何保证? C++ 的强类型系统、编译期检查是我们的安全网
- 性能优化怎么办? 缓存友好性、分支预测、无锁数据结构,这些需要深度理解硬件
- 复杂的模板元编程? SFINAE、Concepts、constexpr,这些高级特性 AI 能驾驭吗?
- 内存管理呢? RAII、智能指针、自定义分配器,一个不小心就内存泄漏
但读完 OpenAI 的文章,我意识到自己问错了问题。
真正的问题不是"AI 能不能写 C++ 代码",而是"我们该如何设计一个让 AI 能写出好代码的环境"。
这是个根本性的思维转变。就像当年从汇编到 C 语言,从 C 到 C++,每一次抽象层次的提升,都不是简单的工具替换,而是整个开发范式的重构。
下面是我从 OpenAI 的实验中提炼出的几个核心洞察,以及它们对 C++ 项目的启示。需要再次强调:OpenAI 的实验很可能不是用 C++ 做的,但他们的方法论对 C++ 项目同样有价值——甚至因为 C++ 的复杂性,这些方法论可能更加重要。
洞察一:从"手艺人"到"建筑师"
回想一下我们是怎么写线程池的。打开编辑器,敲下 class ThreadPool,然后是 std::vector<std::thread>、std::queue、std::mutex、std::condition_variable……每一行都是我们亲手打出来的,每个模板参数都经过深思熟虑。写完之后还要反复检查:析构函数有没有正确 join 所有线程?任务队列的锁粒度够细吗?异常安全吗?
这就是我们熟悉的工作方式。像个手艺人,一刀一刀雕琢代码。
但在 OpenAI 的这个实验里,工程师不再这样工作了。他们做的是另一件事:写规则。
比如要实现线程池,他们会写一份文档:
## 线程池设计原则
- 必须使用 RAII 管理资源
- 任务队列必须是线程安全的
- 必须支持异常传播
- 必须有完整的单元测试覆盖
- 性能要求:任务提交延迟 < 1μs
然后配置一套验证工具:编译器开到 -Wall -Wextra -Werror,跑 AddressSanitizer 和 ThreadSanitizer,用 Google Benchmark 测性能。最后把这些串成一个循环:Agent 生成代码,自动跑测试,检查指标,发现问题,修复,再跑一遍。
听起来很简单?其实这是个巨大的思维转变。我们不再是在"写代码",而是在"设计一个能让 AI 写出好代码的环境"。就像从手工作坊升级到了工业流水线——你不再亲手打磨每个零件,而是设计模具、调试机器、制定质检标准。
这让我想起 C++ 标准委员会的一句话:"We leave no room for a lower-level language."(我们不给更底层的语言留空间)。现在这句话可以改成:"We leave no room for manual coding."(我们不给手写代码留空间)——但前提是,你得先把"规则"这层做扎实。
洞察二:代码是写给谁看的?
这个问题在 C++ 社区争论了几十年。
Bjarne Stroustrup 说过:"C++ is a language for building and using elegant and efficient abstractions."(C++ 是用来构建和使用优雅高效抽象的语言)。我们一直在追求"优雅"——变量名要有意义,函数要短小精悍,注释要恰到好处。Code review 的时候,最常听到的评论是:"这段代码不够清晰"、"这个命名不够直观"。
但"清晰"和"直观"是对谁而言的?是对人类。
现在问题来了:如果代码主要是 AI 在读、在写、在维护,那"清晰"的定义是不是该变一变?
看个例子。我们以前喜欢这样写:
auto calculate_moving_average(const std::vector<double>& data, size_t window_size) {
// ...
}
简洁,现代,用了 C++11 的 auto。人类一眼就能看懂。
但对 AI 来说,这代码有点"模糊"。返回类型是什么?std::vector<double> 还是 double?参数有没有其他配置项?如果要改成支持指数加权平均,是加个 bool 参数还是重载一个函数?
Agent-First 的写法会是这样:
struct MovingAverageConfig {
size_t window_size;
bool use_exponential_weighting;
};
std::vector<double> calculate_moving_average(
const std::vector<double>& input_data,
const MovingAverageConfig& config
);
类型明确,意图清晰,扩展性强。对人类来说可能有点"啰嗦",但对 AI 来说,这就是一份清晰的合同:输入是什么,输出是什么,配置项有哪些,一目了然。
这让我想起 C++ 从 C++98 到 C++11 的演变。当年引入 auto 的时候,很多老派程序员也觉得"类型不明确了"。但后来大家发现,在某些场景下(比如迭代器),auto 反而让代码更清晰。现在轮到 AI 了,也许我们又要重新审视一遍:什么时候该用 auto,什么时候该显式写出类型。
标准不是一成不变的,而是随着"读者"的变化而变化。当你的主要"读者"从人类变成 AI 时,代码风格自然也要跟着调整。
洞察三:架构的铁律
C++ 项目做大了之后,最头疼的是什么?循环依赖。
你在 module_a.h 里 include 了 module_b.h,然后在 module_b.h 里又 include 了 module_a.h。编译器报错,你开始加前向声明,加 PIMPL,把头文件拆成三个……折腾半天,终于编译通过了。过了两个月,新来的同事又在不经意间引入了新的循环依赖。
还有头文件包含混乱。一个 .cpp 文件 include 了 20 个头文件,但实际只用到了 3 个。剩下 17 个是怎么来的?可能是复制粘贴来的,可能是历史遗留的,也可能是"反正加上也不碍事"。结果就是编译时间越来越长,依赖关系越来越乱。
这些问题的根源是什么?缺乏强制的架构约束。
OpenAI 的实验给出了一个激进的解决方案:把架构规则写死,用工具强制执行。他们设计了一个严格的分层模型:
business-domain/
├── types/ # 纯数据结构,零依赖
├── config/ # 配置层,只能依赖 types
├── repo/ # 数据访问层,只能依赖 config
├── service/ # 业务逻辑层,只能依赖 repo
├── runtime/ # 运行时层,只能依赖 service
└── ui/ # 界面层,只能依赖 runtime
规则很简单:只能向前依赖。types 不依赖任何人,config 只能依赖 types,repo 只能依赖 config 和 types,以此类推。违反了?编译失败。没有例外,没有"这次特殊情况"。
这让我想起 John Lakos 在《Large-Scale C++ Software Design》里提出的"levelization"概念。那本书写于 1996 年,快 30 年了,但核心思想依然适用:大型 C++ 项目必须有清晰的层次结构,否则迟早会陷入依赖地狱。
区别在于,以前这些规则靠的是"纪律"和"code review"。现在有了 AI,你可以写个自定义 linter,让它自动检查每一次提交。就像 include-what-you-use 工具一样,但更严格,更针对你的项目。
C++20 引入了 Modules,本质上也是在解决同样的问题:让依赖关系更清晰,让编译更快。但 Modules 只是语言层面的工具,真正的架构约束还是要靠人来设计和执行。在 Agent-First 的世界里,这些约束不再是"建议",而是"铁律"——因为 AI 需要这些铁律才能安全地工作。
有意思的是,这种严格的约束反而带来了自由。当你知道每个模块的边界在哪里,依赖关系是单向的,你就可以放心地让 AI 去改代码,不用担心它会把整个系统搞乱。就像高速公路上的护栏,看起来是限制,实际上是让你能开得更快。
洞察四:"无聊"技术的逆袭
OpenAI 的报告里有句话让我印象深刻:"Boring technology tends to be easier for agents to model."("无聊"的技术更容易被 Agent 理解)
这话听起来有点反直觉。我们这些程序员,尤其是 C++ 程序员,不都喜欢追新吗?C++11 的 lambda、C++14 的泛型 lambda、C++17 的 fold expression、C++20 的 Concepts……每次标准更新,我们都迫不及待地想试试新特性。
但"新"和"酷"不代表"好用"。
还记得 Boost 吗?那个在 C++11 之前几乎是标准库扩展的存在。boost::shared_ptr、boost::function、boost::thread……很多后来都进了标准库。为什么?因为它们经过了时间的考验,文档完善,行为可预测,社区里到处都是使用案例。
对 AI 来说,这些"老古董"反而是最好理解的。训练数据里见过无数次,各种边界情况都有人踩过坑,StackOverflow 上有成千上万的问答。相比之下,那些刚出来的 header-only 库,虽然代码简洁、性能优秀,但 AI 可能根本没见过,或者只见过几次,理解起来就费劲了。
这让我想起 Dan McKinley 在 2015 年写的那篇著名博客《Choose Boring Technology》。他的观点是:每个团队都有有限的"创新代币"(innovation tokens),应该把它们花在真正重要的地方,而不是在技术栈的每一层都追求新潮。
现在这个观点有了新的维度:当你的团队成员包括 AI 时,"无聊"技术的价值更高了。因为 AI 的"学习成本"和人类不一样——它不能像人类那样读一遍文档就理解新库的设计哲学,它需要在训练数据里见过足够多的例子。
具体到 C++ 项目,这意味着什么?
优先用标准库。std::vector 比你自己写的动态数组更好,不是因为性能(可能你的实现更快),而是因为 AI 对 std::vector 的理解远超你的自定义类。它知道 push_back 可能触发重新分配,知道 reserve 可以避免多次分配,知道 emplace_back 比 push_back 更高效。
显式优于隐式。std::make_unique<T>() 比裸指针 new T() 更好,不仅因为异常安全,还因为意图明确。AI 看到 make_unique 就知道这是在创建一个独占所有权的对象,看到裸指针就得猜:这是要手动管理内存?还是指向栈上的对象?还是某个容器的元素?
避免过度聪明的代码。比如这段:
template<typename... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<typename... Ts> overloaded(Ts...) -> overloaded<Ts...>;
这是 C++17 的一个经典技巧,用来实现 std::variant 的 visitor。很优雅,很简洁,在 CppCon 的演讲里经常看到。但对 AI 来说,这代码就像天书。它得理解变参模板、继承、using 声明、推导指引……每一层都有坑。
更好的选择是老老实实写个 visitor 类:
struct Visitor {
void operator()(const TypeA& a) { /* ... */ }
void operator()(const TypeB& b) { /* ... */ }
};
啰嗦?是的。但清晰、可预测、容易理解。
这不是说我们要退回到 C++98 时代,而是说要重新思考"优雅"的定义。在人类主导的时代,"优雅"可能意味着简洁、巧妙、充分利用语言特性。在 AI 参与的时代,"优雅"可能更多地意味着清晰、显式、容易推理。
Bjarne Stroustrup 有句名言:"C++ is a language for professionals."(C++ 是专业人士的语言)。现在这句话可以加个注脚:专业人士不仅要写出能工作的代码,还要写出 AI 能理解和维护的代码。
洞察五:让 AI 看到运行时
传统的 C++ 开发流程是什么样的?写代码,编译,运行,出 bug 了,加几行 std::cout 或者 printf,重新编译,再运行,看输出,猜问题在哪里。如果是多线程的 bug,可能还得上 gdb,设断点,单步调试,看调用栈……
这个过程很慢,很依赖经验。老手可能一眼就看出问题,新手可能要折腾半天。
OpenAI 的实验里,他们做了件很聪明的事:把运行时的所有信息都暴露给 AI。
不是简单的日志,而是结构化的、可查询的、关联的数据。他们给每个 git worktree 配了一套完整的可观测性栈:日志用 LogQL 查询,指标用 PromQL 查询,trace 用 TraceQL 查询。AI 可以自己启动应用,触发某个功能,观察日志和指标,发现问题,修改代码,重启应用,再验证一遍。
整个过程是自动化的。人类只需要说:"确保服务启动时间小于 800ms",或者"这四个关键用户路径的任何 span 都不能超过 2 秒"。AI 会自己去测,去查数据,去优化。
这让我想起 C++ 社区这些年在可观测性上的进步。从最早的 printf 调试,到后来的 gdb 和 valgrind,再到现在的 AddressSanitizer、ThreadSanitizer、UndefinedBehaviorSanitizer……每一次工具的进步,都让我们能更快地发现和修复问题。
但这些工具大多是给人类用的。输出是给人类看的,操作是人类手动触发的。现在如果要让 AI 用这些工具,就得把它们"结构化"。
比如,不要这样写日志:
std::cout << "Debug: value = " << value << std::endl;
而是这样:
spdlog::info("cache_operation",
spdlog::arg("operation", "hit"),
spdlog::arg("key", key),
spdlog::arg("latency_us", latency),
spdlog::arg("cache_size", cache.size())
);
区别在哪里?第一种是给人类看的,第二种是给机器解析的。AI 可以用 LogQL 查询:"过去 5 分钟内,所有 latency_us > 1000 的 cache_operation"。然后它会发现某些 key 特别慢,可能是哈希冲突,或者是缓存淘汰策略有问题。
这种结构化的可观测性,在人类主导的时代是"nice to have",在 AI 参与的时代是"must have"。因为 AI 不能像人类那样"看一眼日志就知道哪里不对劲",它需要精确的、可查询的数据。
更进一步,OpenAI 的团队还让 AI 能直接操作浏览器。通过 Chrome DevTools Protocol,AI 可以打开页面,点击按钮,截图,检查 DOM,观察网络请求……就像一个真实的用户在用产品。发现 bug 了,它会录个视频展示问题,然后修复,再录个视频展示修复后的效果。
这在 C++ 的世界里可能不太常见(我们大多做后端或系统级开发),但思路是相通的:让 AI 能够自己验证代码的正确性。对 C++ 项目来说,这可能意味着:
自动化的性能测试。不是人工跑 benchmark,而是让 AI 自己跑,自己分析结果,自己找瓶颈。
自动化的内存检查。不是人工跑 valgrind,而是让 AI 在每次提交后自动跑 AddressSanitizer,发现内存泄漏就自动修复。
自动化的并发测试。不是人工写压测脚本,而是让 AI 自己生成各种并发场景,用 ThreadSanitizer 检查数据竞争。
关键是建立反馈循环。AI 做了改动,立刻能看到效果,好还是坏,快还是慢,有没有引入新的 bug。这个循环越快,AI 学得越快,改进得越快。
洞察六:知识要活在代码库里
OpenAI 团队踩过一个坑:他们一开始写了一个巨大的 AGENTS.md 文件,把所有规则、约定、最佳实践都塞进去。结果发现根本不管用。
为什么?因为 AI 的上下文窗口是有限的。一个几千行的文档塞进去,就把真正重要的东西——当前的任务、相关的代码、具体的需求——挤出去了。而且,当所有东西都标记为"重要"时,就没有什么是真正重要的了。
更糟糕的是,这种大文档会迅速腐烂。今天加一条规则,明天改一条约定,后天发现某条已经过时了但没人删……几个月后,这文档就变成了一堆互相矛盾的碎片。
他们的解决方案很简单:把 AGENTS.md 变成目录,把知识分散到结构化的文档里。
docs/
├── architecture/ # 架构设计
│ ├── overview.md
│ ├── threading-model.md
│ └── memory-management.md
├── design-decisions/ # 设计决策记录
│ ├── why-no-exceptions.md
│ └── smart-pointer-guidelines.md
├── performance/ # 性能相关
│ ├── benchmarks.md
│ └── optimization-guide.md
└── references/ # 外部参考
├── cpp-core-guidelines.md
└── stl-best-practices.md
AGENTS.md 本身只有 100 行左右,就像一本书的目录,告诉 AI:"如果你要了解线程模型,去看 docs/architecture/threading-model.md;如果你要知道为什么我们不用异常,去看 docs/design-decisions/why-no-exceptions.md"。
这种组织方式有个专业术语,叫"渐进式披露"(progressive disclosure)。不是一次性把所有信息扔给你,而是先给你一个地图,让你知道信息在哪里,需要的时候再去查。
这让我想起 C++ 标准库的设计。你不需要一次性理解整个 STL,你可以先学 std::vector,然后是 std::map,然后是迭代器,然后是算法……每一步都建立在前一步的基础上。标准库的文档也是这样组织的:有概览,有详细的类参考,有使用示例,有性能保证。
对 C++ 项目来说,这意味着什么?
架构文档要结构化。不要写一个 50 页的 Word 文档,而是写一系列互相链接的 Markdown 文件。每个文件专注一个主题,有清晰的标题和章节。
设计决策要记录下来。为什么选择这个数据结构而不是那个?为什么用多线程而不是异步 IO?这些决策背后的权衡是什么?写成 ADR(Architecture Decision Record),放在版本控制里。
代码和文档要同步。最好的方式是让 AI 来做这件事。定期跑一个"文档清理"任务,让 AI 检查文档是否还反映了代码的实际行为,过时的就更新或删除。
这里有个深层的洞察:在 Agent-First 的世界里,如果 AI 看不到,就等于不存在。
那些在 Google Docs 里的设计文档?AI 看不到。
那些在 Slack 里的讨论?AI 看不到。
那些在你脑子里的隐性知识?AI 更看不到。
只有在代码库里的、结构化的、版本控制的知识,AI 才能访问、理解、应用。
这其实是个好事。因为它强迫我们把知识显性化、文档化。以前我们可能会想:"这个设计决策太简单了,不用写文档",或者"这个约定大家都知道,不用特别说明"。现在不行了,你得写下来,因为 AI 不会读心术。
而且,这些文档不仅对 AI 有用,对人类也有用。新人入职,看看 docs/ 目录,就能快速了解项目的架构、设计哲学、编码规范。比起口口相传或者"看代码自己悟",这种方式高效得多。
有人可能会说:"写文档太费时间了。"但在 Agent-First 的模式下,文档本身也可以由 AI 来写。你只需要给个大纲,或者指定要覆盖哪些内容,AI 会去读代码,去理解设计,然后生成初稿。你再审核、修改、补充,比从零开始写要快得多。
这就是一个正向循环:文档越完善,AI 工作越高效;AI 工作越高效,就能帮你维护更好的文档。
从哪里开始?
看到这里,你可能会想:"这些听起来都不错,但我的项目已经有几十万行代码了,不可能推倒重来。"
确实,OpenAI 的实验是从零开始的,他们有这个奢侈。但对大多数人来说,我们面对的是遗留代码、技术债、紧迫的 deadline。怎么办?
答案是:渐进式改造。不要想着一步到位,而是一点一点地引入 AI,让它先做最简单、最机械的事情。
第一步:让 AI 写测试
这是最安全的起点。测试代码相对独立,出了问题也不会影响生产环境。而且,C++ 项目普遍缺测试——我见过太多"测试覆盖率 < 30%"的项目了。
你可以这样开始:
"为 src/cache.cpp 中的 LRUCache 类生成完整的单元测试。要求:使用 Google Test,覆盖所有 public 方法,包含边界条件测试,包含多线程安全性测试。"
AI 会生成一堆测试用例。你审核一遍,跑一遍,发现有些测试失败了?很好,说明你的代码可能有 bug,或者 AI 对需求的理解有偏差。修正,再跑,直到全部通过。
这个过程中,你会发现 AI 能想到一些你没想到的边界情况。比如,空容器的行为、并发访问的竞态条件、异常情况下的资源清理……这些都是容易被忽略的细节。
第二步:让 AI 写文档
代码有了测试,下一步是文档。
"分析 src/ 目录下的所有头文件,生成架构文档。包括:模块依赖图、每个类的职责说明、关键数据流。"
AI 会读你的代码,理解结构,然后生成一份初稿。这份初稿可能不完美,可能有些地方理解错了,但它给了你一个起点。你只需要修改、补充、完善,比从零开始写要快得多。
而且,这个过程本身就是一次代码审查。你会发现一些设计上的问题:"咦,这个模块怎么依赖了那么多东西?"、"这个类的职责是不是太多了?"
第三步:让 AI 重构代码
有了测试和文档,你就可以放心地让 AI 动代码了。
"重构 src/legacy/ 目录。要求:消除所有循环依赖,将裸指针替换为智能指针,添加 const 正确性,保持 API 兼容性。"
AI 会一个文件一个文件地改。每改一个,跑一遍测试,确保行为没变。如果测试失败了,AI 会自己分析原因,修复,再跑。
这个过程可能需要几个小时,甚至一整天。但你不需要盯着,你可以去做别的事情。等 AI 完成了,你再审核一遍,看看改动是否合理。
第四步:让 AI 实现新功能
到这一步,你已经建立了一定的信任。AI 能写测试,能写文档,能重构代码,而且质量还不错。现在可以让它试试实现新功能了。
"实现一个线程安全的对象池。要求:支持泛型(模板),使用 RAII 管理生命周期,性能:获取对象 < 100ns,包含完整测试和 benchmark。"
AI 会先设计接口,然后实现,然后写测试,然后写 benchmark。你审核每一步,给反馈,AI 根据反馈调整。几轮迭代后,你得到了一个可用的对象池。
这个过程比你自己写要快吗?不一定。但它释放了你的时间和注意力。你不需要陷入实现细节,不需要纠结"这里该用 std::lock_guard 还是 std::unique_lock",你只需要关注设计和质量。
关键是建立反馈循环
每一步都要有验证。AI 写了代码,立刻编译、测试、检查。有问题,立刻反馈给 AI,让它改。这个循环越快,AI 学得越快,你的效率越高。
这就是为什么自动化工具链这么重要。如果每次改动都要手动编译、手动跑测试、手动检查内存泄漏,那这个循环就太慢了。但如果你有 CI/CD,有自动化的 sanitizer 检查,有性能回归测试,那 AI 可以在几分钟内完成一个完整的迭代。
AI 还做不好的事
说了这么多 AI 能做的,也该说说它做不好的。
底层性能优化。当你需要榨取最后 5% 的性能时,AI 往往帮不上忙。它可能会建议你用 std::vector::reserve 来避免重新分配,或者用 emplace_back 代替 push_back,这些都是教科书级别的优化。但真正的性能瓶颈往往在更深的地方:缓存行对齐、分支预测、SIMD 指令选择、内存预取……这些需要对硬件有深入的理解,需要反复测量和调优,AI 目前还做不到。
复杂的模板元编程。C++ 的模板系统是图灵完备的,你可以用它做编译期计算、类型推导、SFINAE 技巧……这些东西连人类都觉得难,AI 就更难了。它可能能写出简单的模板类,但涉及到高级技巧时,往往会卡住或者生成错误的代码。
系统级编程。如果你在写操作系统内核、设备驱动、实时系统,AI 能帮上的忙就很有限了。这些领域有太多隐性的约束:不能用动态内存分配、不能用异常、中断处理函数有严格的时间限制……AI 的训练数据里这类代码太少,它很难理解这些特殊的要求。
架构设计。AI 可以实现你设计好的架构,但它不能替你做架构决策。用微服务还是单体?用多线程还是异步 IO?用关系数据库还是 NoSQL?这些决策需要权衡业务需求、团队能力、技术栈成熟度……AI 可以给你分析各种方案的优劣,但最终的决策还是要人来做。
这些限制会一直存在吗?不一定。模型在快速进化,今天做不到的事情,明天可能就能做到了。但至少在可预见的未来,这些领域还是需要人类专家的。
这其实是个好消息。因为这意味着,C++ 工程师不会被 AI 替代,而是会被 AI 增强。
那些机械的、重复的、有明确规则的工作,交给 AI。那些需要创造力、需要深度理解、需要权衡取舍的工作,还是人类的领地。
你的价值不再是"能写出编译通过的代码",而是"能设计出优雅的架构"、"能做出正确的技术决策"、"能解决别人解决不了的性能问题"。
从某种意义上说,这是一次升级。我们从"码农"升级成了"架构师",从"写代码"升级成了"设计系统"。
一个真实的案例
让我讲个更具体的故事。
假设你有个老项目,C++98 写的,十几年没大改过。代码里到处是裸指针、手动 new/delete、没有 RAII、没有单元测试。编译的时候一堆警告,但"反正能跑",就一直拖着。
现在你想改造它,但又不敢动——万一改出 bug 怎么办?万一破坏了什么隐含的依赖怎么办?
这时候 AI 就派上用场了。
第一步:建立基线
让 AI 先分析现状:"分析这个项目,生成依赖关系图、代码质量报告、测试覆盖率报告、技术债务清单。"
AI 会告诉你:哪些模块之间有循环依赖,哪些函数特别长(超过 200 行),哪些地方有潜在的内存泄漏,哪些地方没有测试覆盖……
这份报告本身就很有价值。它让你对项目的健康状况有了量化的认识,而不是凭感觉。
第二步:补测试
不要急着改代码,先补测试。让 AI 为核心模块生成单元测试,优先级是:核心数据结构 > 算法模块 > 工具函数。
这些测试不仅是为了验证当前的行为,更重要的是为后续的重构提供安全网。只要测试通过,你就知道行为没变。
第三步:小步重构
有了测试,就可以开始改代码了。但不要一次改太多,而是小步迭代。
"重构 src/core/memory.cpp:将裸指针替换为 std::unique_ptr 或 std::shared_ptr,将 malloc/free 替换为 new/delete 或容器,将 C 风格数组替换为 std::vector 或 std::array。保持 API 兼容性,添加测试验证行为不变。"
AI 会一点一点地改。改完一个函数,跑一遍测试。测试通过了,继续下一个。测试失败了,分析原因,修复,再跑。
这个过程可能需要几天,但它是安全的。每一步都有验证,每一步都可以回滚。
第四步:持续改进
重构完成后,不要停下来。设置一个定期任务,让 AI 每周检查一次:
- 有没有新增的编译警告?修复。
- 有没有过时的依赖?更新。
- 有没有性能热点?优化。
- 文档是否还反映代码的实际行为?更新。
这就像给项目配了个"自动保养"系统。你不需要时刻盯着,但它会帮你保持代码库的健康。
结果
几个月后,你的项目焕然一新:
- 代码从 C++98 升级到了 C++17
- 内存管理从手动变成了自动(智能指针 + RAII)
- 测试覆盖率从 < 30% 提升到 > 90%
- 编译警告从几百个降到了零
- 文档从"基本没有"变成了"结构完整"
更重要的是,你没有花太多时间在这些机械的工作上。你的时间花在了更有价值的地方:设计新功能、优化关键路径、指导团队成员。
这就是 Agent-First 的威力。
写在最后
OpenAI 的这个实验,对我最大的触动不是"AI 能写代码",而是它重新定义了什么是"好的工程实践"。
以前我们说"好的代码",指的是:命名清晰、逻辑简洁、性能优秀。这些标准是以人类为中心的。
现在我们要加上新的维度:代码是否容易被 AI 理解?架构是否有明确的约束?知识是否结构化地存储在代码库里?反馈循环是否足够快?
这些标准不是为了 AI,而是为了我们自己。因为当你按照这些标准去做时,你会发现:
代码变得更清晰了。类型明确、依赖显式、意图清楚,不仅 AI 容易理解,人类也容易理解。
架构变得更健壮了。严格的分层、强制的约束、自动化的检查,让系统不容易腐化。
知识变得更可传承了。结构化的文档、版本控制的设计决策,让新人能快速上手,让老人不会成为"单点故障"。
开发变得更高效了。自动化的测试、自动化的验证、自动化的重构,让你能专注于真正重要的事情。
这些不都是我们一直追求的吗?只是以前我们做不到,或者做起来成本太高。现在有了 AI,这些变得可行了。
回到最开始的问题:C++ 工程师会被 AI 替代吗?
我的答案是:不会。但我们的工作会变。
我们不再是"写代码的人",而是"设计系统的人"。不再是"修 bug 的人",而是"建立防止 bug 机制的人"。不再是"实现功能的人",而是"定义什么是好功能的人"。
这是一次升级,不是替代。
就像当年从汇编语言到 C 语言,从 C 语言到 C++,每一次抽象层次的提升,都让我们能做更复杂的事情,解决更大的问题。现在轮到 AI 了,它又把我们推到了一个新的层次。
Bjarne Stroustrup 在设计 C++ 的时候说过一句话:"C++ makes programming more enjoyable for serious programmers."(C++ 让严肃的程序员更享受编程)。
也许有一天,我们会说:"AI makes engineering more enjoyable for serious engineers."(AI 让严肃的工程师更享受工程)。
那一天,可能比我们想象的更近。
结尾
我是 everystep 的作者。
我写这些文章,只做一件事:
把工程里的底层问题讲清楚。
这篇是我在更新的专栏:《把计算机拆开给你看》。
每一篇都会挑一个工程里真实会遇到的问题,从现象讲到原理,再落到可复用的实现。
如果你想补齐"从代码到系统"的视角,欢迎关注。
后台回复 「C++基础」,可以领取 PDF 手册。
也可以看看这几篇实战长文:
- 用现代 C++ 手搓一个 RISC-V 模拟器
- 想用现代 C++ 写点"真家伙"?来手搓一个 mini-Redis。
- 想知道 Git 背后到底在干嘛?这篇"从零实现 Git"讲透了。
- 用现代 C++ 从零手写智能指针。
如果你对某个话题感兴趣,或者正卡在某个具体问题上,直接在评论区告诉我。
你的每一次关注、在看和转发,都是我继续写下去的动力。