8、文档生成:Doxygen 注释规范,自动生成 API 文档,ReadTheDocs 与 Sphinx 集成

说实话,很多团队在项目初期都不重视文档。代码写完了,文档?再说吧。结果呢?半年后新人接手,看着一堆没注释的接口直挠头。我自己就吃过这个亏——有一次接手一个遗留系统,光理解一个模块的调用关系就花了两周。

所以这一章,我们来聊聊怎么把文档这件事,做得既专业又省力。核心就三件事:写好注释、自动生成、持续发布

8.1 Doxygen 注释规范:让代码自己说话

Doxygen 是目前 C++ 项目最主流的文档生成工具。它支持多种注释风格,但我个人推荐使用 Javadoc 风格,因为可读性最好,也最通用。

8.1.1 基本注释格式

一个标准的 Doxygen 注释块,长这样:

/**
 * @brief 计算两个向量的点积
 * @param v1 第一个向量
 * @param v2 第二个向量
 * @return 点积结果
 * @throws std::invalid_argument 如果向量维度不一致
 */
double DotProduct(const std::vector<double>& v1, const std::vector<double>& v2);

嗯,这里要注意:@brief 是必须的,它会在生成的文档中作为摘要显示。我见过有人把整个函数逻辑都写在 @brief 里,其实没必要——摘要就一句话,详细说明放到 @details 里。

8.1.2 常用标签一览

标签 用途 示例
@brief 简短描述 @brief 初始化网络连接
@param 参数说明 @param timeout 超时时间(ms)
@return 返回值说明 @return 成功返回0,失败返回错误码
@throws 异常说明 @throws std::runtime_error 连接失败
@note 注意事项 @note 该函数不是线程安全的
@see 参考链接 @see Disconnect()
我的习惯:每个公开接口都写 @brief、@param、@return。私有方法可以只写 @brief。内部实现细节不要写在注释里——代码会变,注释不会,容易产生误导。

8.1.3 类注释与分组

对于类,我建议用 @ingroup 做分组。这在大型项目中特别有用——你想想看,几百个类散落在文档里,谁找得到?

/**
 * @ingroup network
 * @brief TCP 客户端连接管理类
 * @details 封装了 socket 的创建、连接、收发数据等操作。
 *          内部使用非阻塞 I/O 模型。
 */
class TcpClient {
public:
    /**
     * @brief 连接到指定服务器
     * @param host 主机名或 IP
     * @param port 端口号
     * @return true 连接成功,false 连接失败
     */
    bool Connect(const std::string& host, uint16_t port);
    
private:
    int sockfd_;
};

8.2 自动生成 API 文档:Doxygen 配置与运行

注释写好了,怎么生成文档?Doxygen 需要一个配置文件。你可以用 doxygen -g 生成默认配置,然后改几个关键项。

8.2.1 核心配置项

# 项目信息
PROJECT_NAME           = "My C++ Project"
PROJECT_BRIEF          = "高性能网络库"

# 输出格式:HTML + LaTeX
GENERATE_HTML          = YES
GENERATE_LATEX         = NO

# 递归扫描子目录
RECURSIVE              = YES

# 提取所有实体(包括静态函数、私有成员)
EXTRACT_ALL            = YES
EXTRACT_PRIVATE        = YES
EXTRACT_STATIC         = YES

# 使用中文
OUTPUT_LANGUAGE        = Chinese

# 生成调用关系图
HAVE_DOT               = YES
CALL_GRAPH             = YES
CALLER_GRAPH           = YES
我曾经踩过的坑:把 HAVE_DOT 设为 YES 但没装 Graphviz,结果 Doxygen 跑了一小时没反应。记得先 apt install graphvizbrew install graphviz

8.2.2 运行与集成

配置好了,一行命令搞定:

doxygen Doxyfile

输出在 html/ 目录下,用浏览器打开 index.html 就能看到完整的 API 文档。我一般会在 CI 脚本里加这一步:每次合并到 main 分支时,自动生成文档并部署到内部服务器。

8.3 ReadTheDocs 与 Sphinx 集成:不止是 API 文档

Doxygen 生成的文档,说白了就是接口说明书。但一个完整的项目文档,还需要用户指南、架构设计、FAQ 这些内容。这时候就需要 Sphinx 出场了。

Sphinx 是一个文档生成工具,最初为 Python 设计,但通过 Breathe 插件可以完美集成 Doxygen 的输出。

8.3.1 整体架构

先看一张图,理解整个文档生成流程:

文档生成流程架构图 C++ 源码 含 Doxygen 注释 Doxygen 生成 XML 中间文件 Breathe 插件 解析 XML 为 Sphinx 指令 手写 .rst 文档 用户指南、架构设计等 合并 最终 HTML / PDF 文档 部署到 ReadTheDocs 源码 工具链 手写内容 输出

8.3.2 集成步骤

第一步,安装工具:

pip install sphinx breathe sphinx_rtd_theme

第二步,在 Sphinx 的 conf.py 中配置:

# conf.py
import os
import sys

extensions = [
    'breathe',
    'sphinx.ext.autodoc',
]

# Breathe 配置:指向 Doxygen 生成的 XML 目录
breathe_projects = {
    "MyProject": "./doxygen_output/xml/"
}
breathe_default_project = "MyProject"

# 使用 ReadTheDocs 主题
html_theme = "sphinx_rtd_theme"

第三步,在 .rst 文件中引用 Doxygen 生成的接口:

API 参考
========

.. doxygenclass:: TcpClient
   :project: MyProject
   :members:
   :undoc-members:

这样,Sphinx 就会自动从 Doxygen 的 XML 输出中提取 TcpClient 类的文档,并嵌入到最终页面中。

8.3.3 部署到 ReadTheDocs

ReadTheDocs 是一个免费的文档托管平台。你只需要在项目根目录放一个 .readthedocs.yaml

# .readthedocs.yaml
version: 2

build:
  os: ubuntu-22.04
  tools:
    python: "3.11"
  apt_packages:
    - doxygen
    - graphviz

python:
  install:
    - requirements: docs/requirements.txt

sphinx:
  configuration: docs/conf.py

然后,在 ReadTheDocs 后台关联你的 GitHub 仓库。每次 push 代码,它就会自动拉取、运行 Doxygen、调用 Sphinx 构建,最后发布文档。

关键点:一定要在 apt_packages 里加上 doxygen 和 graphviz。我见过有人忘了加,结果构建失败,日志里报 "dot not found",排查了半天。

8.4 避坑指南

最后,分享几个我实际项目中遇到的坑:

  • 注释与代码不同步——我曾经在一个模块里改了接口参数,但忘了更新 @param,结果文档里还是旧参数。后来我养成了习惯:改代码的同时改注释,哪怕只改一行。
  • Doxygen 配置太复杂——默认配置有上千行,别怕。你只需要关注我上面列的那几个核心项,其他保持默认就行。
  • 中文乱码——如果源码文件是 UTF-8 编码,记得在 Doxyfile 里设置 INPUT_ENCODING = UTF-8。我遇到过 GBK 编码的文件,折腾了好久。
  • 文档太大,构建超时——ReadTheDocs 有构建时间限制。如果项目很大,可以考虑只生成关键模块的文档,或者用 EXCLUDE 排除测试代码。

文档这件事,说白了就是「写一次,受益无数次」。花半天时间把 Doxygen + Sphinx + ReadTheDocs 的流水线搭好,以后每次提交代码,文档自动更新。团队里的新人来了,直接扔一个文档链接,省了多少口舌?


公众号:蓝海资料掘金营,微信 deep3321