11. Inline Variables in C++17
In this chapter, you’ll see how to enhance and simplify code using inline variables from C++17.
About static data members
In general, each and every instance (object) of a class has non-static data members as its own data fields; each instance is separate from the other. If we consider a type (a class) representing a Fruit and it has a data member named “mass”, then each particular instance of that Fruit class has a “mass” member belonging to it. If we have 10 Fruit objects, the “mass” data member is replicated ten times. On the other hand, each type can also have static data members that are not bound to any instance of the class. In the case of our Fruit class, we can specify a so-called static variable named “default mass”, accessible to each Fruit instance, but it wouldn’t be part of any instance. In other words, it’s like a global variable in the namespace of the Fruit type.
Consider the following example:
#include <iostream>
struct Value {
int x;
static int y;
};
int Value::y = 0; // definition
int main() {
Value v { 10 };
std::cout << "sizeof(int): " << sizeof(int) << '\n';
std::cout << "sizeof(Value): " << sizeof(Value) << '\n';
std::cout << "v.x: " << v.x << '\n';
Value::y = 10;
std::cout << "Value::y: " << Value::y << '\n';
}
When you run this program, you’ll see the following output:
sizeof(int): 4
sizeof(Value): 4
v.x: 10
Value::y: 10
static int y declared in the scope of the Value class created a variable that is not part of any Value type instance. You can see that it doesn’t contribute to the size of the whole class. It’s the same as the size of the int type.
In the further sections, let’s consider a more practical use case for such class members.
Motivation for inline variables
In C++11/14, if you wanted to add a static data member to a class, you needed to declare it and define it later in one compilation unit. In the example from the previous section, we defined it in the same compilation unit as the main() function. Commonly, such variables are defined in the corresponding implementation file.
For example:
// a header file:
struct OtherType {
static int classCounter;
// ...
};
// implementation, cpp file
int OtherType::classCounter = 0;
This time we also used Wandbox online compiler - as it’s easy to create and compile multiple files:
As you can see above, classCounter is an int, and you have to write it twice: in a header file and then in the CPP file.
The only exception to this rule (even before C++11) is a static constant integral variable that you can declare and initialize in one place:
class MyType {
static const int ImportantValue = 42;
};
You do not have to define ImportantValue in a CPP file.
Fortunately, C++17 gave us inline variables, which means we can define a static inline variable inside a class without defining them in a CPP file.
// a header file, C++17:
struct OtherType {
static inline int classCounter = 0;
// ...
};
The compiler (and the linker) guarantees that there’s precisely one definition of this static variable for all translation units that include the class declaration. Inline variables remain static class variables, so they will be initialized before the main() function is called.
This feature makes it much easier to develop header-only libraries because there’s no need to create CPP files for static variables or use hacks to keep them in a header file (for example, by creating static member functions with static variables inside).
See the example below:
// CountedType.h
struct CountedType {
static inline int classCounter = 0;
// simple counting... only ctor and dtor implemented...
CountedType() { ++classCounter; }
~CountedType() { --classCounter; }
};
And the main() function:
#include <iostream>
#include "CountedType.h"
int main() {
{
CountedType c0;
CountedType c1;
std::cout << CountedType::classCounter << '\n';
}
std::cout << CountedType::classCounter << '\n';
}
The code above declares classCounter inside CountedType, which is a static data member. The class is defined in a separate header file. Thanks to C++17, we can declare the variable as inline. Then, there’s no need to write a corresponding definition later. Without inline, the code wouldn’t compile.
Later, in the main() function, the example creates two objects of CountedType. The static variable is incremented when there’s a call to the constructor. When an object is destroyed, the variable is decremented. We can output this value and see the current count of objects.
Exercise for inline variables
Content available in the full version of the book.
Global inline variables
Content available in the full version of the book.
Constexpr and inline variables
Content available in the full version of the book.