Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
C++17 STL Cookbook

You're reading from   C++17 STL Cookbook Discover the latest enhancements to functional programming and lambda expressions

Arrow left icon
Product type Paperback
Published in Jun 2017
Publisher Packt
ISBN-13 9781787120495
Length 532 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Jacek Galowicz Jacek Galowicz
Author Profile Icon Jacek Galowicz
Jacek Galowicz
Arrow right icon
View More author details
Toc

Table of Contents (11) Chapters Close

Preface 1. The New C++17 Features FREE CHAPTER 2. STL Containers 3. Iterators 4. Lambda Expressions 5. STL Algorithm Basics 6. Advanced Use of STL Algorithms 7. Strings, Stream Classes, and Regular Expressions 8. Utility Classes 9. Parallelism and Concurrency 10. Filesystem

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 .cpp or .cc suffices). 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 to declare them 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, the inline keyword can be used to make an exception in order to allow 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 a promise from 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 a static 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 is not constructed 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.

You have been reading a chapter from
C++17 STL Cookbook
Published in: Jun 2017
Publisher: Packt
ISBN-13: 9781787120495
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime