In this article, we look at how we can annotate our code so it can be picked up by a documentation tool to provide automatic code documentation for us. There are good and bad ways to do this, and unfortunately, more often than not, code documentation is a dreaded task by programmers and is done with the least amount of effort, resulting in bad and unusable documentation. Thus, in this article, we look at how we can provide good and useful documentation which will benefit not just users but also ourselves.
We look at a tool called Doxygen that will help us not just generate automatic code documentation based on annotations we provide in our source files, but it can also host our user guide for the project itself. With its powerful support for various HTML markup, Markdown support and cross-referencing, it is the de-facto standard tool used by C++ programmers, albeit almost universally hated for its pretty bad look. We will look, therefore, at options to make it look pretty using custom styling.
By the end of this article, you will know how to extend your code with Doxygen commands that can be used to generate documentation quickly, as well as what type of documentation will best serve both you and your users (and what type of documentation to avoid!). You may even develop your own strong opinions about Doxygen and, perhaps, will join the war on tabs vs. spaces, if you have not already picked a side. I’ll explain as we go along, so let’s get started.
Download Resources
All developed code and resources in this article are available for download. If you are encountering issues running any of the scripts, please refer to the instructions for running scripts downloaded from this website.
- Download: linearAlgebraSolverLib-Part4
- Download: meshReaderLib-Part4
In this series
- Part 1: Documenting code; why bother and how to do it right
- Part 2: How to write a 1-page readme file to document your code
- Part 3: How to write a user guide to document your CFD code
- Part 4: How to annotate C++ code for automatic code documentation
In this article
Mixing code and documentation: Good or bad?
When we started out and looked at the different types of documentation, we said that we can generally classify documentation into three separate categories:
- The 1-page readme file
- The user guide
- Documentation mixed into C++ code
In this article, we will look at the last item on the list that we have not explored yet. Before we jump into it, though, I wanted to discuss some general comments about writing documentation directly in code, mainly because there are a lot of bad examples out there.
We looked at an example in the first article in this series, and I wanted to pick this up again to kick-start our discussion. At one point in the code, we looked at the following function:
/// A function that takes in a day, month, and year, and returns a formatted date
/**
* \param[in] day A given day
* \param[in] month A given month
* \param[in] year A given year
*
* \return A formatted date
*/
std::string formatDate(int day, int month, int year) {
std::stringstream date;
date << padStringWithZero(day, 2) << "-";
date << padStringWithZero(month, 2) << "-";
date << padStringWithZero(year, 4);
return date.str();
}
Looking at the function, we discussed that when we pass in values such as day=14
, month=3
, and year=2024
, then this will be transformed by the function into a string of the form 14-03-2024
(and, if you are American and so inclined, switch around lines 11 and 12, and you get 03-14-2024
, happy pi-day everyone!).
Assuming you can guess what the function padStringWithZero()
is doing (which we discussed and provided in the original article), then this function is straightforward. If that is the case (and really, your functions should always be straightforward and easy to understand), then the question becomes, is the documentation on lines 1-8 really required?
To me, the answer is no, and I know that other people have strong feelings about one way or another. Some may argue that regardless of the complexity of the function, you should document it; it may be simple and easy to understand for you, but perhaps not for everyone, so you should document and explain what it does. To be fair, that is the belief I originally shared and I started documenting away my code, but at some point, after seeing a lot of pointless code documentation, I changed my mind.
These days, with AI tools a la Chat-GPT integrated directly into our development environments, you’ll find tools that promise to write the documentation for you directly. If an AI tool is clever enough to write the documentation for you, then probably the documentation is not required (i.e. if a tool can guess what a function can do, then a human can do so too (hopefully)).
The point when I changed my viewpoint is after reading the book Clean Code. Especially the chapter on functions is eye-opening, where it is essentially advocated that functions themselves should document your code. If you apply the rules given in the book and push it to the extreme, then you will write functions that should never have more than 1 argument given to the function and it should not have more than 4 lines of code. In that case, the function name becomes a comment, summarising what the next 4 lines of code will do.
I don’t tend to be that extreme with my functions, but whenever I find myself writing longer functions, I tend to break them down into smaller pieces until the function names themselves become more meaningful. But you can see how functions with a narrow scope can more aptly describe what the code is doing.
So, you see, if you write decent and readable code in the first place, your code becomes your documentation, and people will read your code, not the comments, to understand what is happening. Comments should only ever be used to clarify why you are doing something. If you need comments to describe what you are doing, then you probably ought to rethink your code, a practice known as refactoring and something we’ll touch upon later.
Code documentation best practices
So, having looked at some bad examples of code documentation, which could be eliminated by better (clearer) code in the first place, what are some recommendations for annotating code, or should this be avoided then?
I have found an answer for myself that I am happy with and will advocate in the rest of this article, which is essentially adapted from the brilliantly documented Eigen3 library. Feel free to disagree, or to take the parts you like and modify them to your liking. I think code annotation is important, but we should steer away from documentation that describes what functions are doing and focus more on the user and what they need to know to work with our code (this change in perspective will change how we write our documentation).
In short, we should be using object-orientated programming as a start. If you are not sure what exactly you should be doing, I have provided an entire series on things I believe every CFD programmer should know about C++ which serves as a good starting point. In particular, the article on object-orientated programming will be of help here. Once we start to write object-oriented code, we start dealing with classes, which open-up a whole new array of coding practices that we can follow.
One important coding practice in object-oriented programming is the SOLID principle. Each letter in SOLID has its meaning and for us, the most important here is the first one, S, which stands for the single responsibility principle. Essentially it states that each class should only ever do one thing and one thing only. You should be able to write, in one sentence, using 25 words or less, without using the words if, and, or, but, what the class is doing. If you can’t, your class is doing more than one thing or too long.
As a rule of thumb, try to write classes that have no more than 100 lines of code. This is not always feasible, but if you try to limit yourself in the length of the class, then you likely end up with more focused and clearer classes in the first place. These classes will then, in turn, be easier to understand and thus your code becomes, again, the documentation itself.
What a user needs to know is how to work with that class. They don’t care what each function is doing, they just want to know:
- How can I construct an object for this class and what arguments are required for the constructor
- What functions need to be called, if any, to use the class as intended
- What getter functions are available to extract information from the class and how to use them
If you can provide answers for all three points, then you have documentation that a user can comprehend and use to work with your code directly. This means that we should be concentrating on providing examples that show how to work with our code rather than concentrating on documenting what each line or function is doing.
Let us explore how to do that and apply that to our mesh reader and linear algebra solver library.
Doxygen: automated C++ code documentation
OK, so if you are on board with code annotation to provide documentation, then we need to talk about tools. What should we be using? Thankfully, when it comes to C++, there only really is one tool that everyone seems to be using, and that is Doxygen. I say thankfully, I guess this depends on your viewpoint. The default look of doxygen is rather horrendous, but there are ways to make it look more modern, which we will cover as well. Suffice it to say, that Doxygen is the tool of choice for most programmers, so we will look at it as well here.
One nice feature about Doxygen is that it does not simply provide the functionality to pick up annotated code and then spit out the documentation for it, but it can also be used to store and display your user guide alongside your code documentation. Furthermore, you can create symbolic links between the source code and your user guide, which makes it extremely easy to jump from one document to the other.
I quite like to have my user guide and annotated source code in the same place and it makes sense to use Doxygen for both here. It uses Markdown pretty much exclusively, so we can use the knowledge we gained from writing a 1-page readme file and use it here as well.
For completeness, I should add that the same can be achieved with Sphinx, which we looked at when we wrote a user guide for the mesh reading library, i.e. it too can be used to process code annotation, as well as writing the user guide. It is mostly used in Python, though, and there is no official C++ support but rather shabby user contributions. It is, however, still a really good tool for writing user guides, and used by many projects, even if they are rooted in C++.
In this section, I want to look at a few things to get started with the tool, and how to install and work with it. We will also explore some starter templates that I tend to use for writing my code, which helps me to organise my code into a sensible format that allows for easy code annotation (and subsequent automatic code documentation).
Installation
Installing Doxygen is rather straightforward. You can compile it from source or use an installer, and Doxygen covers both in their documentation. But there are two things we should install alongside Doxygen which will help to write our documentation.
We need to install a LaTeX distribution for our operating system. Use MikTex on Windows and TexLive for UNIX (Linux and macOS). You can use TexLive on WIndow as well, but MikTex, to me, is the superior choice, as it allows you to automatically install missing packages, which is great. Just remember to select this option during installation.
Additionally, we need to install Graphviz, which will help to generate inheritance graphs in Doxygen. We may, or may not, want to generate these graphs, but to see them in action we’ll use them in the examples we look at below. So, install it, which, again, is straightforward following their installation instructions.
Running Doxygen
Doxygen is an executable that you can run to produce your code documentation. Since it is a very powerful tool with many options, it needs to know a few things about your project before it can start putting your code and its comments into some HTML output.
The way that Doxygen has solved this issue is by providing a configuration file. You can create said documentation file using the following command:
doxygen -g
This will create a file called Doxyfile
in the same folder from which you have executed Doxygen (alternatively, you can specify a different file name after the -g
flag, but the name Doxyfile
is pretty widely used from what I can see). It is customary, as we have seen before, to create a folder called docs/
within your project where you keep all documentation-related settings and outputs. So in this case, it would make sense to use the following structure:
root
├── build/
├── docs/
│ └── Doxyfile
├── src/
├── tests/
└── ...
If you want to influence Doxygen in any way, you have to open the Doxyfile
and make modifications here. The file itself contains several thousand lines of possible inputs with documentation about what these inputs achieve, and when you are just starting out with Doxygen for the first time, there is merit in going through all of them to just get an idea of what can be achieved.
But, for the impatient ones among you, I have already collected a list of inputs that we should look at and adjust, which I have provided below with additional comments:
PROJECT_NAME
- Provide the name of your software/library/package.
TAB_SIZE
- I have set it to 2. If you have no strong feelings about your tab size (the amount of whitespace code is indented when displaying that contains tabs), or on the argument tabs vs spaces, then you probably aren’t a real programmer (yet). So what’s your tab size?
PROJECT_LOGO
- If you have one, provide a path to your logo file and it will be shown in your documentation.
OUTPUT_DIRECTORY
- The location where you want to output all your generated documentation. I usually use, very unimaginatively, the name
output
, which will then be atroot/docs/output
, if you use the same folder structure as shown above.
- The location where you want to output all your generated documentation. I usually use, very unimaginatively, the name
INPUT
- This is an important one. This specifies which folders Doxygen should be looking for files that contain code annotation. We will later see that there are essentially two locations that are important; the current folder and the
src/
folder. We can add more than one folder by adding to theINPUT
variable, i.e. we may haveINPUT = .
(for the current directory) and then add to thatINPUT += ../src
(notice that we use relative paths here). Put them on two different lines, i.e.
- This is an important one. This specifies which folders Doxygen should be looking for files that contain code annotation. We will later see that there are essentially two locations that are important; the current folder and the
INPUT = .
INPUT += ../src
RECURSIVE
- This tells Doxygen to search for subdirectories and go through them as well. Assume that we have files with annotated code in
root/src/
and inroot/src/helperClasses
. With the above providedINPUT
s, we’ll only get the files within theroot/src
folder into our documentation. If we also want to see documentation created for files within theroot/src/helperClass
directory, then we either need to add it to theINPUT
as seen above, or setRECURSIVE
toYES
to look for subdirectories automatically.
- This tells Doxygen to search for subdirectories and go through them as well. Assume that we have files with annotated code in
USE_MDFILE_AS_MAINPAGE
- Very helpful when you want to use a Markdown file, say, your 1-page README.md file within your documentation as well. As alluded to previously, Doxygen is not just used for creating documentation from code annotation, but also, to display a user guide or additional code documentation. The
argument allows us to specify a markdown file that should be used as our entry point for the user guide, which can then, for example, link to other markdown files within the documentation.USE_MDFILE_AS_MAINPAGE
- Very helpful when you want to use a Markdown file, say, your 1-page README.md file within your documentation as well. As alluded to previously, Doxygen is not just used for creating documentation from code annotation, but also, to display a user guide or additional code documentation. The
USE_MATHJAX
- If we want to display equations within our documentation, we can do that by enclosing them with
\f$ \f$
. Anything between here will be treated as an equation using LaTeX syntax. To enable equation support, set theUSE_MATHJAX
argument toYES
- If we want to display equations within our documentation, we can do that by enclosing them with
MATHJAX_RELPATH
- Specify the location of the
mathjax
library. If you feel lazy, you can use a content delivery network (CDN), which hosts libraries/packages and we can simply refer to them using an URL provided by the CDNs. In this case, you can go ahead and usehttps://cdn.jsdelivr.net/npm/mathjax@2
which will load the required functionality into Doxygen.
- Specify the location of the
GENERATE_TREEVIEW
- I would recommend setting this to
YES
, this will create a table of contents on the left of the screen for easy navigation.
- I would recommend setting this to
HAVE_DOT
- Set this to yes if you have installed the Graphviz package mentioned above. This will create inheritance diagrams that will show us how classes relate to each other in an inheritance hierarchy.
HTML_EXTRA_STYLESHEET
- If you despise the default look of Doxygen (most do), then you can either create your own theme by providing your own custom
css
file, or use one that you can find online. There are a few decent ones available, one of which we will look at later to demonstrate how easy it is to change the style. This, of course, requires some basic CSS knowledge if you want to fine-tune the look of Doxygen.
- If you despise the default look of Doxygen (most do), then you can either create your own theme by providing your own custom
After we have made our changes within the Doxyfile
, we can run Doxygen on it using the following command (assuming we are within the docs/
folder):
doxygen Doxyfile
This will create all required output within the root/docs/<
, where OUTPUT_DIRECTORY
>/html<OUTPUT_DIRECTORY>
is the folder you have specified within the Doxyfile
. In my case, this results in root/docs/output/html
. Look for the index.html
file, and you can start to browse your documentation within your favourite Browser. There also is another folder root/docs/output/latex
, where you can, theoretically, produce a PDF of your documentation using LaTeX, but I’ve found this to look a bit silly, so I wouldn’t recommend it.
First steps with Doxygen
As alluded to now already in a few places, we annotate our code with specific comments that allow us to tell Doxygen to use the current comment during the generation of a comment. In fact, we have already seen the syntax at play in our opening example above.
Whenever we are writing some code documentation, be it for a class or a function, we typically separate the comment into a header and a body. This is achieved with the following syntax:
/// Title (notice the three forward slashes, instead of teh two mandatory one for a C++ comment)
/**
* Body, can contain any text which will be bundled together with code that follows below.
*/
I have used the triple forward slash here, with the body comment given with the double asterisk. You’ll find that Doxygen is pretty flexible here and gives you a multitude of options, I just happen to prefer this visual style.
Within these comment blocks, we can use special commands for Doxygen that will influence the position and look of the comments. A list of all possible commands is provided in the official documentation. Look through these and click through to their description to see how they can be included. To pick a simple example, look at the \author
command and you’ll find the following example in the documentation:
/**
* \brief Pretty nice class.
* \details This class is used to demonstrate a number of section commands.
* \author John Doe
* \author Jan Doe
* \version 4.1a
* \date 1990-2011
* \pre First initialize the system.
* \bug Not all memory is freed when deleting an object of this class.
* \warning Improper use can crash your application
* \copyright GNU Public License.
*/
class SomeNiceClass {};
They show you a few of these commands in action, not just the author and I’m sure that just looking at this example, you’ll get the point of how to use them. Doxygen will then display these with specific HTML formatting. Using the default settings in Doxygen, the above comment block will be translated into the following output:

Annotating functions
Let’s look at the simplest building block, a function. This may take any number of arguments, which are either inputs or outputs to a function. A function may have a return value or not, and we can tell that to Doxygen using the syntax we have already seen with some additional special commands.
class Interpolation {
public:
/// A function to reconstruct intercell values using a central differencing scheme
/**
* \param[in] leftValue The value provided by the left cell
* \param[in] rightValue The value provided by the right cell
* \return The interpolated value at the cells' face intersection
*/
double centralDifferencing(double leftValue, double rightValue) {
return 0.5 * (leftValue + rightValue);
}
};
We have captured this function within a class as this makes it easier for Doxygen to discover the function to be documented. But you can see that a short function (here 3 lines in total) with a good function name should be enough to document the code. The documentation provided here is 6 lines long, i.e. twice as long as the code itself, and does not add any more clarity to what the function itself is already providing. But we look at this example here to understand how to document functions. The above code will produce the following output:

Annotating classes
Let’s stick with the rather simple Interpolation
class given above for the moment and see how we could document it for users to know how to use it. Just like functions, we stick the comment block outside the class, just before its declaration. Then, Doxygen will associate any comments with that class in the documentation. A simple example may look like this:
/// An interpolation class to reconstruct values at cell interfaces
/**
* Use this class if you want to reconstruct/interpolate values at cell interfaces. Given an array of values
* \f$\mathbf{u}\f$ and taking two elements from that vector at location \f$i\f$ and \f$i+1\f$, i.e. we have
* \f$u_{i}\f$ and \f$u_{i+1}\f$, we can reconstruct the value at the cell's face intersection as
* \f$u_{i+1/2}=0.5(u_{i}+u_{i+1})\f$.
*
* \note This class assumes all cells are equidistantly spaced!
*
* To use this class, you can use the following code:
* \code
* Interpolation interpolation;
* double value = interpolation.centralDifferencing(0.0, 1.0);
* assert(value == 0.5);
* \endcode
*/
class Interpolation {
public:
/// A function to reconstruct intercell values using a central differencing scheme
/**
* \param[in] leftValue The value provided by the left cell
* \param[in] rightValue The value provided by the right cell
* \return The interpolated value at the cells' face intersection
*/
double centralDifferencing(double leftValue, double rightValue) {
return 0.5 * (leftValue + rightValue);
}
};
Here have used two special commands, namely \note
and \code
(as well as its closing command \endcode
). If you read through the description, you notice that we can easily include LaTeX equations using the opening and closing \f$
identifier which is pretty handy. But more importantly, if you look at the documentation, it is written from the user’s perspective in terms of what they need. It shows them what the class can do, what its limitations are (within the \note
section) and finally how to use it (within the \code
section). That’s all we need.
The above documentation provides the following output in Doxygen (I am not showing the same documentation for the function here again, but it is still present within the class’ documentation:

Templates for quick code documentation
In this section, I want to look at some templates I use when writing my code documentation. These templates are a great way of starting to organise your code but feel free to modify them to your liking.
Header files template
For header files, I use the code shown below as a starting point. Have a look through and then we can discuss the specifics below the code itself.
#pragma once
// preprocessor statements
// c++ include headers
// third-party include headers
// include headers from current project
// helper classes
// namespace declaration go here
/**
* \class className
* \brief One line description of class (don't use the following words: if, and, or, but)
* \ingroup groupName
*
* Detailed description of what the class does. Can be one or several paragraphs.
*
* The last paragraph should introduce a code example and show how to use the class
* \code
* // write/copy C++ example code here (and make sure it compiles!)
* \endcode
*/
class className {
/// \name Custom types used in this class
/// @{
/// @}
/// \name Constructors and destructors
/// @{
/// @}
/// \name API interface that exposes behaviour to the caller
/// @{
/// @}
/// \name Getters and setters
/// @{
/// @}
/// \name Overloaded operators
/// @{
/// @}
/// \name Private or protected implementation details, not exposed to the caller
/// @{
/// @}
/// \name Encapsulated data (private or protected variables)
/// @{
/// @}
};
After the header include guard statement (#pragma once
), I provide some section headings where I want to organise code into. There is space for preprocessor statements, for namespaces, and to include header files. I separate the header files based on their origin, i.e., they come from either the C++ standard template or runtime library, third-party packages, or the current project.
Then we have some class description, which contains special commands such as \class
, \brief
, \ingroup
, and \code
. The \class
identifier is just the name of the class and the \brief
command provides us with the opportunity to describe the class in one sentence. It is identical to providing a title using the triple-forward slash syntax in front of the comment block, as we have seen before in the class example.
The \ingroup
argument allows you to group classes into groups, which can add an additional layer of documentation to your project. You will then need to specify some additional documentation for each group elsewhere, typically in a header file somewhere within the src/
directory. For example, you may have a file at src/groupDefinitions.hpp
which contains the following content:
/**
* \defgroup groupNameDefinition Short description of what the group does
*
* Long description of what the group does. You can use any special command here.
*/
In this case, whenever we specify \ingroup groupNameDefinition
somewhere in our header file documentation, then our class definition will create a link the a page where the group definition above will be presented. An example, where this may be useful, is our linear algebra solver library, where we currently only have the Conjugate Gradient method implemented, but we could also implement additional methods such as GMRES, and we may want to group all of these methods into their own group and provide some additional documentation.
Finally, we have already looked at the \code
example, which will simply display code within the documentation, which is useful to demonstrate how to use this class.
Within the class itself, you’ll notice additional \name
commands, which are followed by opening /// @{
and closing /// @}
identifiers. This allows us to group functions within a class together. In this example, we have sections for constructors/destructors, getter and setter functions, functions that are exposed to callers (i.e. the public interface that we use when interacting with the class), any overloaded operators, as well as private/protected implementation details that are not exposed to the caller.
Add sections for custom types and class variables, and we have separated all essential parts of a class into their own subsections. Doxygen will pick up the \name
tag to create separate sections within the class documentation. Functions will then be sorted into these sections based on where they have been declared, and we will see that later when we look at the mesh reading library, as well as the linear algebra solver library.
Source files template
The source files actually don’t need any documentation, i.e. Doxygen will not pick up any documentation here, but I have found it useful to mirror the structure of the header file in my source files as well. This is shown below:
// class declaration
#include "path/to/class/definition.hpp"
// namespace declaration go here
/// \name Constructors and destructors
/// @{
/// @}
/// \name API interface that exposes behaviour to the caller
/// @{
/// @}
/// \name Getters and setters
/// @{
/// @}
/// \name Overloaded operators
/// @{
/// @}
/// \name Private or protected implementation details, not exposed to the caller
/// @{
/// @}
It just allows me to group my implementations into the right section and provides some additional clarity to someone going through this code. In the end, we write documentation for users and programmers, and I have found this to be rather helpful, so even if Doxygen doesn’t do anything with it, it is still helpful for developers.
Additional markdown files
One of the nice features of Doxygen as discussed above is that we can put additional documentation into Doxygen and it will display it alongside the annotated code. It supports documentation written in Markdown, which we have discussed how to use back when we looked at the 1-page readme file.
This additional documentation can be simply the 1-page readme file that you have taken from the root directory, or, you can write your entire user guide here using several Markdown pages. If that is what you want to achieve, then the below project structure is a good starting point.
root
├── build/
├── docs/
│ ├── userGuide/
│ │ └── ...
│ └── Doxyfile
├── src/
├── tests/
└── ...
We have added the userGuide/
folder to our docs/
folder and this, in turn, can contain one or more files written in Markdown. Then, to let Doxygen know that we want to use them as well as part of the documentation built, we have to tell it where to find these. This is done, again, with the INPUT
variable within the Doxyfile
. There are two ways of how to achieve that. Say we have three files; introduction.md
, installation.md
, and usage.md
, and they are all located in the userGuide/
folder. Then, we may have, within our Doxyfile:
INPUT = userGuide/
INPUT += ../src
This works fine but may not give you the desired effect. If you have turned on the Treeview by setting
, then you will see that the order of your pages is GENERATE_TREEVIEW = YES
installation
, introduction
, and usage
. The reason is, that Doxygen sorts your input files alphabetically, but we want to specify the order. Thus, you may want to specify the order in which to include the files instead and that allows you to influence the order in which they will be displayed. Hence, we would write:
INPUT = userGuide/introduction.md
INPUT += userGuide/installation.md
INPUT += userGuide/usage.md
INPUT += ../src
If you want to ensure that, for example, userGuide/introduction.md
is the first page that you want to see when opening the documentation, then you have to specify that by setting the variable USE_MDFILE_AS_MAINPAGE = userGuide/introduction.md
. That’s all there is to it.
Well, you can probably survive with the above knowledge most of the time, but just be aware that Doxygen is treating some Markdown syntax differently. For example, we saw before that we could use equations by placing them between dollar signs as in $F=ma$
. In Doxygen, this changes to \f$F=ma\f$
.
In true Markdown fashion, Doxygen themselves have extended the non-existing Markdown standard with their own set of non-standard Markdown syntax extensions, and it is worth having a look through the Markdown support page in the Doxygen documentation, which I just did, and where I learned that it has support for creating a table of contents automatically. Happy days!
Making references
Having both annotated code and additional Markdown files in your project will inevitably require you to reference between the two. Thankfully, this is possible and also rather straightforward. Say you are annotating your header files and in one instance you feel it would be good to refer the reader to the user guide, and a specific section. We can achieve that with references.
On the Markdown side, add a reference tag to the end of your heading, as in the following example:
## Important heading to be referenced {#referenceToHeading}
Then, in your source file, you can use this tag together with the \ref
command as in the following example:
/// Important class
/**
* \brief Short description of class
*
* Some detailed description, which can be augmented with \ref referenceToHeading to get additional details.
*/
This is a basic, but probably rather often used, example, of how to reference between documents. Doxygen has, however, support for linking to source files as well, and it gives you fine-grained control over how you want to link to not just a file but also the functions within it. A full list of Doxygen’s capabilities can be found in the documentation with additional examples.
Replacing the outdated look with a modern style
Doxygen always used to look, let’s just say, very humble. It seems that with version 1.9.5, they have tried to mask the somewhat ugly interface with a dark mode option, and while does look slightly better, it is still rather horrendous.
Luckily for us, Doxygen is nothing more than some static HTML, styled with CSS (well, an attempt of it anyway), and some additional dynamic behaviour added with JavaScript. If you are into web development, you will recognise this as how websites were created a few decades ago (I’m exaggerating, but you get my point). Anyways, for us, this means that changing the look and feel of Doxygen is not that difficult. If you feel comfortable with CSS, this is all you have to change. But if not, a quick look around will reveal a few alternative themes.
One of the themes I have come to like over the years, and one which seems to be used by quite a few people is the Doxygen awesome theme. Changing the look of Doxygen could not be simpler. First, download the latest release of Doxygen awesome, extract it, and place it within your docs/
folder.
Next, find the HTML_EXTRA_STYLESHEET
variable within your Doxyfile
and add the required CSS files, which we can get from he documentation. Using Doxygen awesome, our Doxyfile
would now have the following entries:
HTML_EXTRA_STYLESHEET = doxygen-awesome-css-2.3.2/doxygen-awesome.css
HTML_EXTRA_STYLESHEET += doxygen-awesome-css-2.3.2/doxygen-awesome-sidebar-only.css
That’s all. Recompile the documentation using the command doxygen Doxyfile
invoked from within the docs/
folder, and you will now have arrived in the future, congratulations. Well, it is not really futuristic, but in comparison to the default look, it certainly has improved. Shop around for some additional themes and see what you like. There are some themes out there worth checking out, but for the rest of this article, I’ll stick with Doxygen awesome.
Adding Doxygen to our existing libraries
As always, I think it is best to look at an example that we can examine by looking at the source (here the Doxygen documentation Markdown pages, as well as the annotated code) and then observe its behaviour (i.e. the generated HTML output). Using all of the knowledge above, I have gone ahead and created annotated code documentation for both the mesh reader library and the linear algebra solver library.
I have compiled the documentation using both the default and Doxygen awesome themes so that you can get a feeling for the differences. There is not much else to add here, mainly what I want to bring across is how the final documentation would look, and reading through it may give you a better idea of how to annotate real projects and how to provide additional information in the form of a user guide.
You can, as always, download the relevant examples at the beginning of this article to inspect the source files yourself. Reading both the annotated source code and the generated HTML documentation should give you a clear idea of how to achieve certain things, but I have covered all of them in this article, so hopefully, there are no surprises.
Linear algebra solver library
For the linear algebra library, I have simply reused the README.md file, which we have placed in the root directory as our 1-page readme file. I needed to make a copy and put it into our docs/
folder, as it contains some Markdown syntax that Doxygen does not support and needs to be modified. In particular, I am talking here about how equations are handled, and as we saw before, in Doxygen, we have to replace $ $
with \f$ \f$
to render equations. Setting USE_MDFILE_AS_MAINPAGE = README.md
, we are seeing this file first when opening our documentation.
The annotated code follows the starter template we have seen above, and I have simply filled in some information to annotate the code. The following shows the output generated by Doxygen using the two different themes.
- Linear algebra solver library documentation (Default Doxygen theme)
- Linear algebra solver library documentation (Doxygen awesome theme)
In the Treeview displayed on the left, you see the main section headings from our README.md
file. At the bottom, Doxygen also puts links to our Classes
and Files
, which we can use to get the documentation for a specific class or file. You can also use the search bar at the top to search for a specific feature. Browse around the documentation and see if it makes sense. You will find that many other software using Doxygen will then feel very similar.
Mesh reader library
For the mesh reader library, I have followed the approach from our previous article, where we have broken up our README.md
into several parts to create a user guide. I have used these separate pages and included them with the INPUT
variable as discussed above. I have also created an additional file called index.md
which I use as the entry point into my documentation. It essentially just displays the table of contents, which I have learned now should not be generated manually but rather using the \tableofcontents
or [TOC]
command.
Similar to the linear algebra solver library above, I have created two versions with our two different themes, and you can have a look at the corresponding documentation using the links below:
- Mesh reader library (Default Doxygen theme)
- Mesh reader library (Doxygen awesome theme)
Summary
Congratulations, if you have made it all the way through to this point and read the previous 3 articles as well, you have probably finished the most boring (yet rather important) series on code documentation. I get it, we love to write code but writing documentation is, at best, boring and at worst, a hated part of the entire coding process.
Let’s not underestimate the importance of code documentation, though. There is a saying that we write comments and documentation not for others, but rather for ourselves. 6 months from now, if we look at a piece of code we haven’t touched in these 6 months, we may not understand anymore what we did or how to use our code. This is exactly the missing piece that documentation should provide. Show people how to use your code and give rationales where needed.
In this article, we looked at the somewhat polarising software Doxygen (see: Doxygen: Worst thing to ever hit documentation?, Doxygen is driving me insane, and I hate documenting source code), but I’d argue it is a necessary evil and given its sheer amount of options and functionalities, Doxygen is here to stay for a long time and going to be the undisputed tool of choice for documenting C++ code for a long time. The sooner we are OK with that, the sooner we can get on with our lives and incorporate it into our coding workflow.

Tom-Robin Teschner is a senior lecturer in computational fluid dynamics and course director for the MSc in computational fluid dynamics and the MSc in aerospace computational engineering at Cranfield University.