Book Image

C++17 STL Cookbook

By : Jacek Galowicz
Book Image

C++17 STL Cookbook

By: Jacek Galowicz

Overview of this book

C++ has come a long way and is in use in every area of the industry. Fast, efficient, and flexible, it is used to solve many problems. The upcoming version of C++ will see programmers change the way they code. If you want to grasp the practical usefulness of the C++17 STL in order to write smarter, fully portable code, then this book is for you. Beginning with new language features, this book will help you understand the language’s mechanics and library features, and offers insight into how they work. Unlike other books, ours takes an implementation-specific, problem-solution approach that will help you quickly overcome hurdles. You will learn the core STL concepts, such as containers, algorithms, utility classes, lambda expressions, iterators, and more, while working on practical real-world recipes. These recipes will help you get the most from the STL and show you how to program in a better way. By the end of the book, you will be up to date with the latest C++17 features and save time and effort while solving tasks elegantly using the STL.
Table of Contents (18 chapters)
Title Page
Credits
About the Author
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface
Index

Enabling header-only libraries with inline variables


While it was always possible in C++ to declare individual functions inline, C++17 additionally allows us to declare variables inline. This makes it much easier to implement header-only libraries, which was previously only possible using workarounds.

How it's done...

In this recipe, we create an example class that could suit as a member of a typical header-only library. The target is to give it a static member and instantiate it in a globally available manner using the inline keyword, which would not be possible like this before C++17:

  1. The process_monitor class should both contain a static member and be globally accessible itself, which would produce double-defined symbols when included from multiple translation units:
       // foo_lib.hpp 

       class process_monitor { 
       public: 
           static const std::string standard_string 
               {"some static globally available string"}; 
       };

process_monitor global_process_monitor;
  1. If we now include this in multiple .cpp files in order to compile and link them, this would fail at the linker stage. In order to fix this, we add the inline keyword:
       // foo_lib.hpp 

       class process_monitor { 
       public: 
           static const inline std::string standard_string 
               {"some static globally available string"}; 
       };

inline process_monitor global_process_monitor;

Voila, that's it!

How it works...

C++ programs do often consist of multiple C++ source files (these do have.cppor.ccsuffices). These are individually compiled to modules/object files (which usually have .o suffices). Linking all the modules/object files together into a single executable or shared/static library is then the last step.

At the link stage, it is considered an error if the linker can find the definition of one specific symbol multiple times. Let's say, for example, we have a function with a signature such as int foo();. If two modules define the same function, which is the right one? The linker can't just roll the dice. Well, it could, but that's most likely not what any programmer would ever want to happen.

The traditional way to provide globally available functions is todeclarethem in the header files, which will be included by any C++ module that needs to call them. The definition of every of those functions will be then put once into separate module files. These are then linked together with the modules that desire to use these functions. This is also called the One Definition Rule (ODR). Check out the following illustration for better understanding:

However, if this were the only way, then it would not have been possible to provide header-only libraries. Header-only libraries are very handy because they only need to be included using #include into any C++ program file and then are immediately available. In order to use libraries that are not header-only, the programmer must also adapt the build scripts in order to have the linker link the library modules together with his own module files. Especially for libraries with only very short functions, this is unnecessarily uncomfortable.

For such cases, theinlinekeyword can be used to make an exceptionin order toallow multiple definitions of the same symbol in different modules. If the linker finds multiple symbols with the same signature, but they are declared inline, it will just choose the first one and trust that the other symbols have the same definition. That all equal inline symbols are defined completely equal is basically apromisefrom the programmer.

Regarding our recipe example, the linker will find the process_monitor::standard_string symbol in every module that includes foo_lib.hpp. Without the inline keyword, it would not know which one to choose, so it would abort and report an error. The same applies to the global_process_monitor symbol. Which one is the right one?

After declaring both the symbols inline, it will just accept the first occurrence of each symbol and drop all the others.

Before C++17, the only clean way would be to provide this symbol via an additional C++ module file, which would force our library users to include this file in the linking step.

The inline keyword traditionally also has another function. It tells the compiler that it can eliminate the function call by taking its implementation and directly putting it where it was called. This way, the calling code contains one function call less, which can often be considered faster. If the function is very short, the resulting assembly will also be shorter (assuming that the number of instructions that do the function call, saving and restoring the stack, and so on, is higher than the actual payload code). If the inlined function is very long, the binary size will grow and this might sometimes not even lead to faster code in the end. Therefore, the compiler will only use the inline keyword as a hint and might eliminate function calls by inlining them. But it can also inline some functions without the programmer having it declared inline.

There's more...

One possible workaround before C++17 was providing astatic function, which returns a reference to a static object:

class foo {
public:
    static std::string& standard_string() {
        static std::string s {"some standard string"};
        return s;
    }
};

This way, it is completely legal to include the header file in multiple modules but still getting access to exactly the same instance everywhere. However, the object isnotconstructed immediately at the start of program but only on the first call of this getter function. For some use cases, this is indeed a problem. Imagine that we want the constructor of the static, globally available object to do something important at program start (just as our reciple example library class), but due to the getter being called near the end of the program, it is too late.

Another workaround is to make the non-template class foo a template class, so it can profit from the same rules as templates.

Both strategies can be avoided in C++17.