Introduction: Why Documentation is Code
\n\nIn the world of C++ projects, high-quality documentation often becomes a bottleneck. At a basic level, function comments and a simple README are sufficient. But when a project grows to tens of thousands of lines, introducing templates, smart pointers, complex inheritance hierarchies, and third-party libraries, the question arises: how do you keep the documentation up-to-date without spending half your work time on it?
\n\nThe answer is automation and strict standards. In this article, we will explore advanced techniques for writing C++ documentation: from configuring Doxygen with diagram generation to integrating with Sphinx and automatic verification of code examples. The material is intended for developers already familiar with the basics of documentation who want to elevate the process to a professional level.
\n\n1. Doxygen: Configuration for Complex Projects
\n\nDoxygen remains the de facto standard for C++ documentation. However, its basic configuration often doesn't unlock its full potential. Let's look at key settings for advanced use.
\n\n1.1. Optimizing Doxyfile for Large Projects
\n\nFor projects with thousands of files, it's important to configure performance and output relevance. Add the following to your Doxyfile:
# Include all dependencies\nEXTRACT_ALL = YES\nEXTRACT_PRIVATE = YES\nEXTRACT_STATIC = YES\n\n# Build inheritance and collaboration diagrams\nHAVE_DOT = YES\nDOT_NUM_THREADS = 4\nCLASS_DIAGRAMS = YES\nCOLLABORATION_GRAPH = YES\nINCLUDE_GRAPH = YES\nINCLUDED_BY_GRAPH = YES\n\n# Ignore test files and third-party libraries\nEXCLUDE_PATTERNS = */test/* */third_party/*\nEXCLUDE_SYMBOLS = testing::*\n\nTip: Use DOT_IMAGE_FORMAT = svg for vector diagrams — they scale without loss of quality and support interactive links.
1.2. Documenting Templates and Concepts
\n\nTemplate code is difficult to read. Doxygen allows you to document template parameters using \\tparam:
/**\n * @brief Serializes a container to JSON.\n *\n * @tparam Container Container type supporting iteration.\n * @tparam Serializer Functor for serializing elements.\n * @param container Source data.\n * @param serializer Serialization object.\n * @return std::string JSON string.\n *\n * @code\n * std::vector<int> data = {1, 2, 3};\n * auto result = serialize_to_json(data, [](int x) { return x; });\n * @endcode\n */\ntemplate <typename Container, typename Serializer>\nstd::string serialize_to_json(const Container& container, Serializer serializer);\n\nFor C++20 concepts, use @concept:
/**\n * @concept Serializable\n * @brief A type that can be serialized to a string.\n *\n * Requires the method `std::string serialize() const`.\n */\ntemplate <typename T>\nconcept Serializable = requires(T t) {\n { t.serialize() } -> std::same_as<std::string>;\n};\n\n2. Sphinx + Breathe: Documentation as a Website
\n\nDoxygen generates static HTML, but for complex projects it's more convenient to use Sphinx — a system that creates beautiful, structured websites with search, cross-references, and support for reStructuredText or Markdown. Integration with C++ is achieved through the Breathe extension.
\n\n2.1. Setting up the Doxygen + Sphinx Combination
\n\nFirst, configure Doxygen to generate XML:
\n\nGENERATE_XML = YES\nXML_OUTPUT = xml\n\nThen install the Python packages:
\n\npip install sphinx breathe\n\nCreate the Sphinx configuration (conf.py):
extensions = ['breathe']\nbreathe_projects = {"MyProject": "xml/"}\nbreathe_default_project = "MyProject"\n\n\nNow