C++ API documentation

Introduction

In C++, the de-facto standard for API documentation is Doxygen. Like many API documentation tools, it works by parsing the program’s source code, looking for specially formatted code comments that precede the declaration of the code entity (type, function…) that is being documented.

// The following code comment is parsed by Doxygen as a function description,
// since it comes right before a function declaration.
//
// Notice how the \param directive can be used to document individual function
// parameters. You should not overuse this functionality: keep it for situations
// where the function documentation is not, on its own, complete enough for
// users to infer parameter semantics.

/*!
 * Compute the squared Euclidean norm of `vec`.
 *
 * In linear algebra, the norm of a vector quantifies how far away from the
 * origin the vector extends in the underlying vector space. Among possible
 * norms, one will most commonly use the Euclidean norm or 2-norm, which is
 * the square root of the sum of squared vector coordinates.
 *
 * In low-dimensional spaces, computing the square root is much more expensive
 * than other parts of the norm computation. Therefore, it is recommended to use
 * the squared norm over the actual Euclidean norm whenever your use case allows
 * for it.
 *
 * \param[in] vec Coordinates of a vector in an n-dimensional Euclidean space.
 */
double norm2(const std::vector<double>& vec);

This is a good design choice for an API documentation system because it brings the documentation as close as possible to the code entity that is being documented, which reduces the odds that as the code evolves, developers will forget to update associated API documentation.

However, this also means that Doxygen a poor fit for documentation which is not as closely related to the source code, such as user tutorials, as using it for this purpose would drown the source code into heaps of long comments which are not strongly related to the program’s main job. We will later discuss another documentation tool, mdBook which is better suited to this sort of documentation.

Setting up Doxygen

First of all, you need to install the doxygen command line tool. For the purpose of this training, we have done it for you in the virtual machines on which the practicals are un. But in most Linux distributions, this is most easily done by using the doxygen package from your distribution’s standard package manager. Other operating systems may require finding an alternate source of pre-built binaries, or even building Doxygen from source.

After installing Doxygen, the next step will be to create a configuration file for your project. This is most easily done by using the doxygen -g command, which will create a Doxyfile in the current directory that contains all configuration options, pre-set to mostly reasonable default values. The configuration file uses a simple KEY = VALUE format, supplemented by comments that explain what each configuration option does.

Options that you may want to adjust away from the defaults include…

  • EXTRACT_PACKAGE = YES: This ensures that every source file gets documented, even in absence of a file-wide documentation comment. This avoids the puzzling situation where you added documentation to some code entity but it doesn’t appear in the Doxygen output. In programming languages with a source/header dichotomy like C++, this setting is best complemented with removing every source file extension from the FILE_PATTERNS list.
  • QT_AUTOBRIEF = YES: This ensures that the first sentence of an entity’s documentation is interpreted as a summary, which is a good practice in general, and better than the visually noisy alternative of needing explicit \brief tag inside of each and every doc comment.
  • SHOW_INCLUDE_FILES = NO: This suppresses the default Doxygen behavior of listing every header included by a source file, which is usually more noisy than informative.

Once Doxygen is configured, running doxygen in your source directory will produce, among other things, an html directory containing a set of HTML pages with your new source code documentation. It is advised to add this directory, along with other automatically generated Doxygen output, to your project’s .gitignore version control exclusion rules.

Another optional step which you may want to take is to set up your build system so that documentation can be built together with the source code in a single command. This ensures that the documentation is regularly built in your everyday development workflow, and errors in the documentation are therefore noticed quickly.

The apidoc/ subdirectory of the source code examples demonstrate a full setup that uses the aforementioned configuration, based on the GNU Make build system. Running make in this directory (or the parent directory) will automatically rebuild both the provided C++ programs and the associated Doxygen documentation.

Writing the API documentation

A complete guide to Doxygen comments would be out of scope for this quick tutorial, we will refer you to the official manual for this. But here is a complete header file that demonstrates some key features of Doxygen that you may be interested in when starting out:

#pragma once


// It is a good idea to start a header file with a Doxygen comment that explains
// what it is about, what common theme unifies all functionality implemented by
// the underlying code module.
//
// If you can't find a common theme that can be summarized in a single short
// sentence, your code module is probably trying to do too many things and would
// benefit from being split into several simpler code modules.
//
// Notice the following Doxygen syntax points:
//
// - Comments are flagged for Doxygen consumption using a marker character, in
//   this course we are using the Qt style of Doxygen comment where the marker
//   character is an exclamation mark.
// - We need to clarify that we are documenting the entire source file, rather
//   than some specific code object, using the \file directive.
// - Thanks to the QT_AUTOBRIEF = YES configuration option, the first sentence
//   of a Doxygen comment is automatically treated as a summarized description
//   of the entity that is being documented.

/*!
 * \file
 *
 * This file demonstrates basic usage of Doxygen for API documentation purpose.
 *
 * We will use it to demnstrate notions such as file-wide documentation,
 * function documentation, type documentation, and so on.
 */

#include <vector>


// The following code comment is parsed by Doxygen as a function description,
// since it comes right before a function declaration.
//
// Notice how the \param directive can be used to document individual function
// parameters. You should not overuse this functionality: keep it for situations
// where the function documentation is not, on its own, complete enough for
// users to infer parameter semantics.

/*!
 * Compute the squared Euclidean norm of `vec`.
 *
 * In linear algebra, the norm of a vector quantifies how far away from the
 * origin the vector extends in the underlying vector space. Among possible
 * norms, one will most commonly use the Euclidean norm or 2-norm, which is
 * the square root of the sum of squared vector coordinates.
 *
 * In low-dimensional spaces, computing the square root is much more expensive
 * than other parts of the norm computation. Therefore, it is recommended to use
 * the squared norm over the actual Euclidean norm whenever your use case allows
 * for it.
 *
 * \param[in] vec Coordinates of a vector in an n-dimensional Euclidean space.
 */
double norm2(const std::vector<double>& vec);


// Doxygen can also document much more complex code entities, such as class
// hierarchies.

/*! This is the base class of all evil
 *
 * It has no destructor because evil cannot be destroyed, only displaced.
 */
class EvilBase {
  public:
    /*! Automatically set up the evilness */
    EvilBase() = default;

    /*! Query the current level of evilness */
    int evilness() const { return m_evilness; }

  protected:
    /*! Manually configure the level of evilness (for experts only) */
    explicit EvilBase(int evilness) : m_evilness(evilness) {}

  private:
    /*! Current level of evilness */
    int m_evilness = 42;
};


/*! Travel management system powered by the souls of unborn children
 *
 * Hand washing is strongly advised after prolonged contact with instances of
 * this class. In case of accidental ingestion, seek immediate medical
 * assistance.
 */
class Notilus : public EvilBase {
  public:
    /*! Release the proverbial Kraken */
    Notilus() : EvilBase(666) {}
};

Exercise

Pick any C++ codebase (either one of the source directories supplied as part of this course, other than apidoc obviously, or possible a simple program of yours) and try to set up Doxygen and write some documentation for some of the public entities in the source code.

Conclusion

As you can see, writing API documentation is not terribly difficult, and can be a significant asset to people getting familiar with your program if done right.

However, there is more to documentation than API docs. For example, while it is technically possible to use Doxygen to document higher-level architecture or write tutorials, as done by libraries like hwloc or Eigen, the quirks of Doxygen make this needlessly difficult. Nicer toolchains exist for standalone documentation, as we are going to see in the next chapter on mdbook.