When writing your own library, there are certain best practices that all library authors should adhere to. In this recipe, we will explore some higher-priority best practices and conclude with some information about a project dedicated to defining these best practices, including a registration system that provides your library with a grade as to how well it compiles. This recipe is important as it will teach you how to make the highest-quality library, ensuring a strong and vibrant user base.
Learning library development best practices
Getting ready
As with all of the recipes in this chapter, ensure that all of the technical requirements have been met, including installing Ubuntu 18.04 or higher and running the following in a Terminal window:
> sudo apt-get install build-essential git cmake clang-tidy valgrind
This will ensure your operating system has the proper tools to compile and execute the examples in this recipe. Once you have done this, open a new Terminal. We will use this Terminal to download, compile, and run our examples.
How to do it...
You need to perform the following steps to complete this recipe:
- From a new Terminal, run the following to download the source code:
> cd ~/
> git clone https://github.com/PacktPublishing/Advanced-CPP-CookBook.git
> cd Advanced-CPP-CookBook/chapter01
- To compile the source code, run the following code:
> mkdir build && cd build
> cmake ..
> make recipe04_examples
- Once the source code has been compiled, you can execute each example in this recipe by running the following commands:
> ./recipe04_example01
21862
In the next section, we will step through each of these examples and explain what each example program does and how it relates to the lessons being taught in this recipe.
How it works...
Every library author should ensure their library is easy to use and incorporate into their users' own projects. Doing so will ensure your users continue to use your library, resulting in a growing user base over time. Let's look at a few of these best practices.
What about warnings?
The lowest possible hanging fruit for any library author is ensuring your code compiles with as many warnings enabled as possible. Sadly, GCC does not make this process simple as there is no one warning flag to rule them all, specifically because GCC has many warning flags that are not useful for modern versions of C++ (in other words, they are, in a sense, mutually exclusive). The best place to start is with the following warnings:
-Wall -Wextra -pedantic -Werror
This turns on most of the important warnings while ensuring that any warnings that your examples or tests compile will generate an error. For some libraries, however, this will not be enough. At the time of writing, the following are the flags that Microsoft's Guideline Support Library uses:
-Wall -Wcast-align -Wconversion -Wctor-dtor-privacy -Werror -Wextra -Wpedantic -Wshadow -Wsign-conversion
One additional warning that the GSL uses is conversion warnings, which will tell you when you convert between different integer types. If you are using Clang, this process can be a lot easier as it provides -Weverything. If weeding through all of the warnings that GCC provides is too much work, one approach to solving this issue is to make sure that your library compiles with the Clang compiler with this warning turned on, which will ensure your code compiles with most of the warnings that GCC provides. This way, your users will not have trouble with your library when they have to ensure specific warnings are enabled in their code as you will have tested as many of them as possible.
Static and dynamic analysis
In addition to testing for warnings, libraries should also be tested with static and dynamic analysis tools. Once again, as an author of a library, you must assume your users might use static and dynamic analysis tools to shore up the quality of their own applications. If your library triggers these tools, your users are more likely to look for alternatives that have been tested more thoroughly.
For C++, there is a large number of tools that can be used to analyze your libraries. In this recipe, we will focus on Clang Tidy and Valgrind, which are both free to use. Let's look at the following simple example:
#include <iostream>
int universe()
{
auto i = new int;
int the_answer;
return the_answer;
}
int main()
{
std::cout << universe() << '\n';
return 0;
}
In the preceding example, we created a function called universe() that returns an integer and allocates an integer. In our main function, our universe() function output the results to stdout.
To statically analyze the preceding code, we can use CMake as follows:
set(CMAKE_CXX_CLANG_TIDY clang-tidy)
The preceding line of code tells CMake to use clang-tidy when compiling the preceding example. When we compile the code, we get the following result:
If a user of your library has turned on static analysis using Clang Tidy, this is the error they might receive, even though their code is perfectly fine. If you are using someone else's library and run into this issue, one way to overcome the problem is to include the library as a system include, which tells tools such as Clang Tidy to ignore these errors. This, however, doesn't always work as some libraries require the use of macros, which expose the library's logic to your own code, resulting in chaos. In general, if you are a library developer, statically analyze your library as much as you can afford to as you don't know how your users might use your library.
The same goes for dynamic analysis. The preceding analysis didn't detect the obvious memory leak. To identify this, we can use valgrind, as follows:
As shown in the preceding screenshot, valgrind is able to detect the memory leak in our code. Actually, valgrind also detects the fact that we never initialize our temporary variable in the universe() function, but the output is far too verbose to show here. Once again, if you fail to identify these types of problem with your libraries, you will end up exposing these bugs to your users.
Documentation
Documentation is an absolute must for any good library. Besides buggy code, a lack of documentation will absolutely prevent others from using your library. Libraries should be easy to set up and install, and even easier to learn and incorporate into your own applications. One of the most frustrating aspects of using existing C++ libraries is the lack of documentation.
CII Best Practices
In this recipe, we have touched on a couple of common best practices that all library developers should incorporate into their projects. In addition to these best practices, a more complete list of best practices is provided by the CII Best Practices program here: https://bestpractices.coreinfrastructure.org/en.
The CII Best Practices program provides a comprehensive list of best practices that are updated over time and that library developers (and any application in general) can leverage. These best practices are grouped into passing, silver, and gold, with the gold practices being the hardest to achieve. The higher your score, the more likely users are to use your library as it shows commitment and stability.