- 文档
- 开发工具
【免费下载链接】sphinx
The Sphinx documentation generator
Sphinx 的 C++ 语言域(cpp域)为 C/C++ 项目提供了一套完整的声明式文档标记体系,允许用.. cpp:class::、.. cpp:function::等指令描述类、函数、成员、枚举与类型别名,并借助:cpp:func:等角色实现声明间的交叉引用与重载消歧。本文以仓库中 tests/roots/test-domain-cpp/index.rst 这一测试基座文档为主线,系统拆解每一条 C++ 域指令的语法、语义与底层实现,并给出可复制的实战示例与作用域管理技巧,帮助你为自己的 C++ 项目编写结构完整、可检索、可交叉引用的 API 文档。
一、文档定位:一份 C++ 域指令的“最小完整范例”
tests/roots/test-domain-cpp/index.rst 是 Sphinx 测试套件中domain-cpp测试根的入口文档,配套的测试用例集中在 tests/test_domains/test_domain_cpp.py。虽然它只有五十余行,却几乎覆盖了 C++ 域声明指令的全部分类:
- 类与结构体:
cpp:class、cpp:struct - 函数与成员函数:
cpp:function - 成员变量:
cpp:member、cpp:var - 类型别名:
cpp:type - 枚举与枚举项:
cpp:enum、cpp:enum-class、cpp:enum-struct、cpp:enumerator
在同一目录下,仓库还提供了 roles.rst、roles-targets-ok.rst、roles-targets-warn.rst 等文档,分别验证“角色对目标类型的匹配规则”以及“错误使用角色时应产生的警告”,这些与index.rst共同构成 C++ 域完整的功能图谱。
从源码层面看,全部指令与角色注册在 sphinx/domains/cpp/init.py:
directives = { # declarations 'class': CPPClassObject, 'struct': CPPClassObject, 'union': CPPUnionObject, 'function': CPPFunctionObject, 'member': CPPMemberObject, 'var': CPPMemberObject, 'type': CPPTypeObject, 'concept': CPPConceptObject, 'enum': CPPEnumObject, 'enum-struct': CPPEnumObject, 'enum-class': CPPEnumObject, 'enumerator': CPPEnumeratorObject, # scope control 'namespace': CPPNamespaceObject, 'namespace-push': CPPNamespacePushObject, 'namespace-pop': CPPNamespacePopObject, # other 'alias': CPPAliasObject, }可以看到struct与class复用同一个指令类,var与member也共用CPPMemberObject,而enum-class/enum-struct与enum共用CPPEnumObject。理解了这张注册表,就能明白下文中每一条指令的行为差异。
二、对象声明指令逐一拆解
1.cpp:class:声明类
基础用法直接写在指令后的声明行中,缩进的正文用于描述对象:
.. cpp:class:: public Sphinx The description of Sphinx class.测试文档特意写了public Sphinx——public属于访问限定符,C++ 域解析器会将其正确剥离并用于渲染;类名后也可以追加基类列表:
.. cpp:class:: MyClass : public MyBase, MyOtherBase从源码结构看,CPPClassObject继承自 sphinx/domains/cpp/init.py 中的CPPObject,声明文本会交给_parser.py中的DefinitionParser解析为ASTClass节点,再注册进符号表(_symbol.py中的Symbol树)。这也是“类内嵌套成员”“跨文档引用”能够成立的基础。
cpp:struct与cpp:class共用CPPClassObject,二者的区别只影响默认访问权限与渲染风格,指令语法完全一致。
2.cpp:function:声明(成员)函数
.. cpp:function:: int hello(char *name) The description of hello function.函数原型支持完整的 C++ 语法,包括引用、const限定、默认参数、模板、运算符重载等。文档 doc/usage/domains/cpp.rst 给出的示例包括:
.. cpp:function:: bool myMethod(int arg1, std::string arg2) .. cpp:function:: const T &MyClass::operator[](std::size_t i) const .. cpp:function:: operator bool() const .. cpp:function:: constexpr void foo(std::string &bar[2]) noexcept .. cpp:function:: MyClass::MyClass(const MyClass&) = defaultcpp:function是测试文档中密度最高的一类:index.rst末尾一口气声明了 8 个函数,专门用于验证重载消歧与括号(operator())引用场景:
.. cpp:function:: void paren_1(int, float) .. cpp:function:: void paren_2(int, float) .. cpp:function:: void paren_3(int, float) .. cpp:function:: void paren_4(int, float) .. cpp:function:: void paren_5::operator()(int) .. cpp:function:: void paren_6::operator()(int) .. cpp:function:: void paren_7::operator()(int) .. cpp:function:: void paren_8::operator()(int)配合 roles.rst 中的引用写法,可以完整看到重载消歧的三种策略:
* :cpp:func:`paren_1` # 不带括号引用 * :cpp:func:`paren_2()` # 带括号引用 * :cpp:func:`paren_3_title <paren_3>` # 自定义标题 + 无括号 * :cpp:func:`paren_4_title <paren_4()>` # 自定义标题 + 带括号 * :cpp:func:`paren_5::operator()` # 引用 operator() * :cpp:func:`paren_6::operator()()` # operator() 带括号注意paren_1与paren_2签名完全相同(void(int, float)),属于“任意重载”引用;而文档 doc/usage/domains/cpp.rst 中说明,当需要精确指向某个重载时,可以带上完整的返回类型与参数列表:
:cpp:func:`void C::f()` :cpp:func:`void C::f(int)` :cpp:func:`void C::f(double)` :cpp:func:`void C::f(double) const`底层实现上,交叉引用会先把目标文本解析为查找键(LookupKey),再在符号树中按“名称 → 重载集合”的顺序匹配;_symbol.py中的_DuplicateSymbolError机制保证了同名同签名的重复声明会触发明确报错。
3.cpp:member与cpp:var:声明变量/成员变量
测试文档中的两个示例分别演示了“带限定名的成员变量”与“全局变量”:
.. cpp:member:: float Sphinx::version The description of Sphinx::version. .. cpp:var:: int version The description of version.二者共用CPPMemberObject,语法上等价:cpp:member通常用于成员变量,cpp:var用于普通变量,但解析器对两者一视同仁。Sphinx::version这种类名::成员名的写法会自动把该符号挂到Sphinx类符号之下,因而可以像:cpp:member:Sphinx::version`` 一样按限定名引用。
4.cpp:type:声明类型别名
.. cpp:type:: std::vector<int> List The description of List type.cpp:type对应CPPTypeObject,描述 typedef 或类型别名声明,也支持模板别名(见 doc/usage/domains/cpp.rst)。测试文档中声明了一个以std::vector<int>为底层的别名List,之后便可以用:cpp:type:List`` 交叉引用它。
5.cpp:enum系列:枚举与枚举项
测试文档涵盖了三种枚举形态,正好对应 C++ 的三种枚举声明方式:
.. cpp:enum:: MyEnum An unscoped enum. .. cpp:enumerator:: A .. cpp:enum-class:: MyScopedEnum A scoped enum. .. cpp:enumerator:: B .. cpp:enum-struct:: protected MyScopedVisibilityEnum : std::underlying_type<MySpecificEnum>::type A scoped enum with non-default visibility, and with a specified underlying type. .. cpp:enumerator:: Bcpp:enum:非限定作用域(unscoped)枚举;cpp:enum-class:限定作用域(scoped)枚举;cpp:enum-struct:与cpp:enum-class等价,但允许在声明中带访问限定符(如protected)和显式底层类型(如std::underlying_type<MySpecificEnum>::type),这正是示例中所展示的完整形态。
枚举项通过缩进的.. cpp:enumerator::嵌套在枚举指令内声明。文档 doc/usage/domains/cpp.rst 指出:unscoped 枚举的枚举项会同时注册在枚举自身作用域与外围作用域,因此引用时可以省略枚举名;而 scoped 枚举的枚举项只能通过MyScopedEnum::B这样的限定名引用。枚举项还可以直接携带值:
.. cpp:enumerator:: MyEnum::myOtherEnumerator = 42三、交叉引用角色:让声明之间“可链接”
C++ 域注册的角色(见 sphinx/domains/cpp/init.py)与声明指令一一对应,并额外提供两个表达式角色:
roles = { 'any': CPPXRefRole(), 'class': CPPXRefRole(), 'struct': CPPXRefRole(), 'union': CPPXRefRole(), 'func': CPPXRefRole(fix_parens=True), 'member': CPPXRefRole(), 'var': CPPXRefRole(), 'type': CPPXRefRole(), 'concept': CPPXRefRole(), 'enum': CPPXRefRole(), 'enumerator': CPPXRefRole(), 'expr': CPPExprRole(asCode=True), 'texpr': CPPExprRole(asCode=False), }在index.rst声明的对象,可由同一目录的 roles.rst 交叉引用验证:
* :cpp:class:`Sphinx` * :cpp:member:`Sphinx::version` * :cpp:var:`version` * :cpp:type:`List` * :cpp:enum:`MyEnum`这里体现出的引用规则包括:
- 限定名优先:
Sphinx::version精确指向成员变量;裸名version则解析到全局变量。 - 角色与目标类型匹配:
cpp:enum只能指向枚举,cpp:func只能指向函数。测试 tests/test_domains/test_domain_cpp.py 中的test_domain_cpp_build_misuse_of_roles精确列出了“合法目标类型 → 允许使用的角色”映射表(如class目标允许class/struct/type角色,func目标允许func/type角色),并用roles-targets-warn.rst验证错误用法会触发WARNING: cpp:<role> targets a <type>警告。 - 模板参数需要转义:引用
MyClass<int>会被 Sphinx 解释成“指向int、标题为MyClass”,因此必须写成:cpp:class:MyClass<int>(转义左尖括号),或者改用无需转义的 `:cpp:expr:`MyClass<int>。
另外两个表达式角色适合在正文中嵌入 C++ 表达式:cpp:expr以等宽代码样式渲染并解析为可引用符号,cpp:texpr以普通文本样式渲染但同样参与符号解析。
四、作用域管理:namespace 三指令
默认情况下,所有声明都放在全局作用域。C++ 域提供三条指令管理当前作用域(见 doc/usage/domains/cpp.rst):
.. cpp:namespace:: scope:重置作用域栈并切换到给定作用域;传入NULL、0或nullptr表示回到全局。.. cpp:namespace-push:: scope:在当前作用域基础上相对地压入更深一层。.. cpp:namespace-pop:::撤销最近一次namespace-push(注意不是简单弹出一层)。
.. cpp:namespace:: A::B .. cpp:namespace-push:: C::D # 当前作用域:A::B::C::D .. cpp:namespace-pop:: # 当前作用域:A::B(回到 push 之前)作用域不必严格对应 C++ 命名空间,也可以以类名结尾,例如.. cpp:namespace:: Namespace1::Namespace2::SomeClass::AnInnerClass,此后声明的对象都会自动带上该前缀。跨文件场景下,cpp:namespace配合“先声明类、再在别处声明其成员”的模式非常实用;cpp:alias指令则可以为已存在的声明插入别名,方便统一不同命名下的引用入口。
在index.rst中虽然没有显式使用这三条指令,但其声明的Sphinx::version这类“类限定成员”本质上等价于“把version放进Sphinx作用域”——这正是作用域机制的一种内联形态。
五、匿名实体与符号查找细节
C++ 支持匿名命名空间、类、枚举和联合体。文档 doc/usage/domains/cpp.rst 规定,此类实体必须起一个以@开头的名字(如@data),渲染时统一显示为[anonymous],但引用时既可以显式写全限定名,也可以省略匿名实体名:
.. cpp:class:: Data .. cpp:union:: @data .. cpp:var:: int a .. cpp:var:: double b 显式引用::cpp:var:`Data::@data::a` 快捷引用::cpp:var:`Data::a`从源码结构看,这条“省略中间层查找”的能力由_symbol.py的符号树查找逻辑支撑——Symbol节点在解析嵌套名称时会跳过匿名实体层级,这也正是 tests/roots/test-domain-cpp/anon-dup-decl.rst 与测试test_domain_cpp_build_anon_dup_decl(tests/test_domains/test_domain_cpp.py)所验证的行为。
六、C++ 域常用配置项
在conf.py中可以按需调整 C++ 域的行为(完整定义见 doc/usage/configuration.rst 与注册代码 sphinx/domains/cpp/init.py):
| 配置项 | 类型 / 默认值 | 作用 |
|---|---|---|
cpp_index_common_prefix | Sequence[str]/() | 全局索引排序时忽略的前缀列表,如'awesome_lib::' |
cpp_id_attributes | Sequence[str]/() | 额外接受的“无参数属性”字符串,适用于#define宏定义的属性 |
cpp_paren_attributes | Sequence[str]/() | 额外接受的“带一个参数”的属性,如my_align_as(X)(要求括号/花括号平衡) |
cpp_maximum_signature_line_length | int \| None/None | 签名长度超过该值时每个参数独占一行;None表示不限制 |
cpp_debug_lookup/cpp_debug_show_tree | bool/False | 调试符号查找过程与符号树输出 |
例如,当项目通过#define引入了可移植性属性时:
cpp_id_attributes = [ 'my_id_attribute', ] cpp_paren_attributes = [ 'my_align_as', ] cpp_index_common_prefix = [ 'awesome_lib::', ]cpp_maximum_signature_line_length是域级配置,会覆盖全局的maximum_signature_line_length;签名过长时自动将每个参数换行展示,显著改善函数原型密集页面的可读性。若同时设置了add_function_parentheses = True(全局配置),cpp:func角色引用不带括号的函数名时会在渲染时自动补上(),而引用operator()时该逻辑会被特殊处理以避免重复括号(见 sphinx/domains/cpp/init.py)。
七、实战:从测试基座到自己的 API 文档
把测试文档的骨架迁移到真实项目,一个完整的最小示例长这样:
C++ API 参考 ============ .. cpp:namespace:: mylib .. cpp:class:: public Engine 引擎基类。 .. cpp:function:: void start() 启动引擎。 .. cpp:member:: int rpm 当前转速。 .. cpp:enum-class:: State : std::uint8_t 运行状态。 .. cpp:enumerator:: Idle .. cpp:enumerator:: Running = 1 .. cpp:type:: std::vector<int> Track 轨迹类型别名。 引用示例: * 类::cpp:class:`Engine` * 成员函数::cpp:func:`Engine::start` * 成员变量::cpp:member:`Engine::rpm` * 枚举::cpp:enum:`State` * 枚举项::cpp:enumerator:`State::Running` * 类型别名::cpp:type:`Track`要点回顾:
- 用
cpp:namespace统一声明作用域,减少每个名字前面的重复限定; - 类的成员、枚举项一律缩进嵌套在父指令之下,保证符号层级正确;
- 存在同名重载时,用
:cpp:func:完整签名`` 精确消歧,普通场景直接用函数名即可; - 模板相关引用注意转义尖括号,或改用
cpp:expr/cpp:texpr; - 构建时如遇
WARNING: cpp:<role> targets a <type>,说明角色与目标类型不匹配,参照 roles-targets-warn.rst 的意图修正角色选择。
若想进一步验证自己的写法是否正确,可以参照仓库的测试组织方式:将示例文档放入tests/roots/下的测试根,再用@pytest.mark.sphinx('html', testroot='domain-cpp')形式的测试用例构建并断言输出,这也正是 tests/test_domains/test_domain_cpp.py 覆盖重载、匿名实体、角色误用、add_function_parentheses开关等场景时所采用的做法。
结语
从 tests/roots/test-domain-cpp/index.rst 这五十余行测试文档出发,我们完整梳理了 Sphinx C++ 域的对象声明指令、枚举形态、交叉引用角色、作用域管理与相关配置。这套体系的价值在于:文档中的每个符号都进入统一的符号表,从而获得精确的重载消歧、跨文档引用与索引条目生成能力。掌握这些指令后,你完全可以把一个大型 C++ 代码库的 API 文档组织得结构清晰、可链接、可检索,并借助仓库中现成的测试基座持续回归验证。
- 文档
- 开发工具
【免费下载链接】sphinx
The Sphinx documentation generator
相关推荐
Sphinx C++ 域(cpp domain)完全指南:从实体声明到交叉引用的实战手册
Sphinx C++ 域(cpp domain)完全指南:从实体声明到交叉引用的实战手册 导读 Sphinx 的 C++ 域(domain 名 cpp )为 C
文档开发工具Sphinx 领域 API 详解:用 Domain 体系扩展对象描述指令与交叉引用
Sphinx 领域 API 详解:用 Domain 体系扩展对象描述指令与交叉引用 导读 Sphinx 的"领域(Domain)"是其最核心的可扩展机制之一:一
文档开发工具Sphinx C 域(C Domain)完整指南:声明指令、交叉引用、匿名实体与命名空间
Sphinx C 域(C Domain)完整指南:声明指令、交叉引用、匿名实体与命名空间 C 语言 API 的文档化一直是 Sphinx 的核心能力之一,而承载
文档开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考