When creating a library, it is important to namespace everything. Doing so ensures that of the APIs provided by the library cause name collisions with the user's code or with facilities provided by other libraries. In this recipe, we will demonstrate how to do this in our own libraries.
How to namespace everything
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
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 recipe02_examples
- Once the source code has been compiled, you can execute each example in this recipe by running the following commands:
> ./recipe02_example01
The answer is: 42
> ./recipe02_example02
The answer is: 42
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...
C++ provides us with the ability to wrap code in a namespace, which simply adds the namespace name to all functions and variables inside the namespace code (it should be noted that C style macros are not included in the namespace and should be used with care because C macros are a preprocessor feature that does not contribute to the code's compiled syntax). To explain why we should namespace everything when creating our own libraries, we'll look at some examples.
Example 1
Example 1 demonstrates how to wrap your library's APIs in a C++ namespace :
// Contents of library.h
namespace library_name
{
int my_api() { return 42; }
// ...
}
// Contents of main.cpp
#include <iostream>
int main(void)
{
using namespace library_name;
std::cout << "The answer is: " << my_api() << '\n';
return 0;
}
As shown in the preceding example, the contents of the library are wrapped in a namespace and stored in the header (this example demonstrates a header-only library, which is an extremely useful design approach as the end user doesn't have to compile libraries, install them on his/her system, and then link against them). The library user simply includes the library header file and uses the using namespace library_name statement to unwrap the library's APIs. If the user has more than one library with the same API names, this statement can be omitted to remove any ambiguity.
Example 2
Example 2 expands upon the previous example and demonstrates how to wrap your library's APIs in a C++ namespace header-only library while still including global variables:
// Contents of library.h
namespace library_name
{
namespace details { inline int answer = 42; }
int my_api() { return details::answer; }
// ...
}
// Contents of main.cpp
#include <iostream>
int main(void)
{
using namespace library_name;
std::cout << "The answer is: " << my_api() << '\n';
return 0;
}
As shown in the preceding example, C++17 was leveraged to create an inline global variable that is wrapped in our library's namespace. inline variables are needed as header-only libraries don't have a source file to define global variables; without the inline keyword, defining a global variable in a header would result in the variable being defined multiple times (that is, the result would be a linking error during compilation). C++17 resolved this issue by adding inline global variables, which allows a header-only library to define global variables without the need for tricky magic (such as returning a pointer to a static variable from a singleton style function).
In addition to the library's namespace , we wrapped the global variable in a details namespace. This is done to create a private place within your library in case the user of the library declares using namespace library_name. If the user does this, all of the APIs and variables that are wrapped by the library_name namespace become globally accessible within the scope of the main() function. For this reason, any private APIs or variables that are not meant to be accessible by the user should be wrapped by a second namespace (typically called details) to prevent their global accessibility. Finally, leveraging C++17's inline keyword allows us to create a global variable for use in our library while still supporting a header-only design.