09 - static fields
As stated in a previous chapter, static
modifies object's storage duration.
A class can contain static
members: variables (fields) and functions (methods). static
fields, unlike other static
objects do not have reduced linkage. They can still be used in other TUs.
This lesson uses structures in examples to simplify and shorten code.
Said differently, static
fields are just global objects that are placed inside class namespace.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// foo.hpp struct foo { foo(int x): x(x) {} int x = 0; static int s; // static member }; // foo.cpp int foo::s = 1; // initialization of static member // main.cpp #include <iostream> #include <memory> int main() { foo f1(10); foo f2(20); // static members are shared across all instances std::cout << "f1.s = " << f1.s << "\n"; std::cout << "f2.s = " << f2.s << "\n"; ++f1.s; std::cout << "f1.s = " << f1.s << "\n"; std::cout << "f2.s = " << f2.s << "\n"; // it's actually the same object std::cout << "address of f1.s = " << std::addressof(f1.s) << "\n"; std::cout << "address of f2.s = " << std::addressof(f2.s) << "\n"; // static members are not a part of the object // the class has 2 ints but only 1 contributes to the size std::cout << "sizeof(int) = " << sizeof(int) << "\n"; std::cout << "sizeof(foo) = " << sizeof(foo) << "\n"; // you actually don't need any object to access static members ++foo::s; std::cout << "foo::s = " << foo::s << "\n"; } |
f1.s = 1 f2.s = 1 f1.s = 2 f2.s = 2 address of f1.s = 0x600eb8 address of f2.s = 0x600eb8 sizeof(int) = 4 sizeof(foo) = 4 foo::s = 3
Because static
member objects are global it's recommended to refer to them through the class name, not specific objects. The syntax which uses .
helps in some situations (e.g. templates which don't know on what type they operate) but can be misleading.
In practice
Obviously global state is bad so static
fields should be avoided. A typical example where they can be used is generation of unique IDs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <iostream> class user { public: user(/* params... */) : id(++id_counter) {} int get_id() const { return id; } // other member functions... private: int id; // other fields (e.g. name, password, ...) static int id_counter; }; // even private static members can be initialized this way int user::id_counter = 0; int main() { user u1; // gets id == 1 user u2; // gets id == 2 { user u3; // gets id == 3 } // u3 dies here; id == 3 will not be reused user u4; // gets id == 4 std::cout << u4.get_id() << "\n"; } |
Here each user
object will get a unique ID. The id
member could be const
, but there are good reasons to not use the keyword even if the field never changes (the main one being prevention of assignment of objects of this class).
Static initialization order fiasco
The same fiasco can happen with static
members:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct bar { static int x; }; struct baz { static int y; }; // one translation unit int bar::x = 1; // other translation unit int baz::y = bar::x; |
The solution is the same as with other static
objects - bundle them together into one type and move static
from these objects to the instance of this type:
1 2 3 4 5 6 7 8 9 10 11 12 |
struct barz { barz(int val): x(val), y(val) {} int x; int y; }; struct foo { static barz b; }; barz foo::b(1); |
Syntax sugar
There are ways to avoid the need of initialization outside the class definition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// since C++11 // if the static member is const, it can be initialized in the class definition struct foo { static const int s = 1; // static const is implicitly inline }; // since C++11 // if the static member is constexpr, it has to be initialized in the class definition struct bar { static constexpr int s = 1; // constexpr initialization implies inline }; // since C++17 // static members can be marked inline and initialized in the class definition struct baz { inline static int s = 1; }; |
The C++17 improvement is most significant, because it completely removes the need to write initialization of static
fields outside class definition. Apart from shorter code, it eliminates a common mistake when the initialization was simply forgotten.
Summary
static
fields are affected by access specifiers (except initialization statement).static
fields are not associated with any object. They exist even if no objects of the class have been created.static
fields can not bemutable
.static
fields can also be declaredthread_local
.static
fields are initialized beforemain
starts executing.
Does it mean that through constructor of a static object I can inject a function to be run before main?
Yes. Obviously this should not be abused. Order of intialization of objects with static storage duration is undefined but C++ guarantees that standard library objects are initialized first - this means that you can use std::cout
before main
starts executing.