Reference:
[C++][C++20] consteval / constexpr
[C++/Rust] use of thread_local in code
[Note] linking notes
Initializing order
- Perform constant initialization and zero initialization
- Perform dynamic initialization
- Start executing main() How main() is executed on Linux
- Perform dynamic initialization of function-local static variables, as needed.
- It is impossible to read uninitialized global memory.
- Destroy function-local static variables that were initialized in reverse order
- Destroy other globals in reverse order.
header.h
inline int global = compute();
// Always OK due to sequence; and `h` is inline.
// (compiler will take care of its init.)
inline int h = global;
tu.cpp
#include "header.h"
int a = global; // not certain due to TU not guarantee `global` being defined.
Templated variables are init. at some point before main()
template<typename T>
int type_id = get_id<T>();
template<typename T>
struct templ {
static int type_id = get_id<T>();
};
Compiler is allowed to perform dynamic initialization at compile-time.
int f(int i) {
return 2 * i; // not constexpr
}
int the_answer = f(21); // might happen at compile-time; even not constexpr.
Compiler is allowed to perform dynamic init. after main() has started executing.
i.e. an unused global variable might not be initialized at all.
const auto start_time = std::chrono::system_clock::now();
int main() {
auto t = start_time; // might be init. at this point.
}
The static initialization order fiasco
Dynamic initialization order of globals in different translation units is not specified.
GuidelineUnless otherwise allowed by its documentation, do not access global state in the constructor of another global.
* Solution 1
Globals initialized by some constant expression are initialized during static initialization without dynamic initialization.Constant expressions cannot access globals that are dynamically initialized.
Guideline
Whenever possible, make global variables constexpr.
Guideline
Whenever possible, use constant initialization.
e.g.
Guideline
Declare global variables constinit to check for initialization problems.
Whenever possible, make global variables constexpr.
constexpr float pi = 3.14;
constexpr std::size_t threshold = 3;
Whenever possible, use constant initialization.
// https://en.cppreference.com/w/cpp/thread/mutex/mutex
std::mutex mutex; // Has constexpr constructor.
Always look into the code path that has function which is not constexpr.
e.g.constexpr int compute(bool default) {
if(default)
return 42; // constexpr
else
return std::getchar(); // runtime.
}
int default = compute(true); // constexpr
int non_default = compute(false); // runtime
C++20
constinit = constexpr - const
- variable is initialized using constant initialization (or error)
- variable is not const
constinit std::mutex mutex; // OK.
constinit int default = compute(true); // constexpr; ok.
constinit int non_default = compute(false); // runtime, thus error.
Declare global variables constinit to check for initialization problems.
Guideline
Try to add a constexpr constructor to every type.
Try to add a constexpr constructor to every type.
e.g.
Default constructors of containers(with default allocator)
Default constructors of resource handles(files, sockets, ...)
Default constructors of containers(with default allocator)
Default constructors of resource handles(files, sockets, ...)
Guideline
Do not use constinit if need to do lazy initialization.
Do not use constinit if need to do lazy initialization.
* Solution 2:
Lazy initialization.
Global& global() {
static Global g;
return g;
}
// The global init. is not determinated.
Global& global = global(); // Bad because can't be constinit
Never cache a (reference to) a function-local static in a global variable.
template<typename Tag, typename T>
class lazy_init {
public:
constexpr lazy_init() = default;
T& get() {
static T global;
return global;
}
T& operator*() { return get();}
T* operator->() { return &get();}
};
constinit lazy_init<TAG, Logger> global; // OK
global->log(); // The first call takes time, beware.
logger.hclass Logger{...};
extern constinit lazy_init<TAG, Logger> global;
logger.cppconstinit lazy_init<TAG, Logger> global;
Global variable destruction
Global variables are destroyed in reverse dynamic initialization order.Beware, constinit always initialized first, and that is its purpose.
A a;
constinit B b;
void func(){
static C c;
}
int main() {
func();
}
init. binit. a
init. c
destroy c
destroy b
destroy a
Destruction order of globals in different TUs is not specified.
GuidelineUnless otherwise allowed by its documentation, do not access global state in the destructor of another global.
This applies to constinit globals.
Rule
header.h
Use manual initialization either for all globals in your project, or none.
And remember to initialize them all!
- The order of dynamic initialization is not specified.
-
Exception: within one translation unit, variables are initialized from top to bottom.
1) Must include the header that declares the global before using it
2) every global defined in that header is initialized before your global. - Do not use function-local static variables if there's a chance they might be used after main()
- Do not use function-local static variables.
template<typename Tag, typename T>
class lazy_init {
public:
constexpr lazy_init() = default;
T& get() {
// technique; 1) thread safe, 2) initialize data member in one-shot.
static bool dummy = (storage_.initialize(), true);
return storage_.get();
}
private:
storage<T> storage_;
};
constinit lazy_init<struct global_tag, std::string> global;
Nifty counters
- Ensure the nifty counter object is included in the definition file.
- The nifty counter approach doesn't work for templated objects.
header.h
extern constinit nifty_init<Global> global_init; // declaration; definition is in some .cpp file.
// static int dummy = (global_init.initialize(), 0);
static nifty_counter_for<global_init> dummy; // definition
inline constinit Global& global = global_init.reference();
constinit Global copy = global; // compiler error
a.cpp#include "header.h"
int a = global->foo();
b.cpp#include "header.h"
int b = global->foo();
template<typename T>
class nifty_init {
public:
constexpr nifty_init() = default;
void initialize() {
if (counter_++ == 0)
storage_.initialize();
}
void destroy() {
if (--counter_ == 0)
storage_.get().~T();
}
constexpr T& reference() { return storage_.get(); }
private:
int counter_ = 0;
storage<T> storage_;
};
template<auto& NiftyInitT>
struct nifty_counter_for {
nifty_counter_for() {
NiftyInitT.initialize();
}
~nifty_counter_for() {
NiftyInitT.destroy();
}
};
Conclusion
- constinit is not always applicable
- lazy initialization has to leak
- nifty counters are black magic
- Or, simply don't do anything before or after main()
Manual initialization (Google's way)
GuidelineUse manual initialization either for all globals in your project, or none.
And remember to initialize them all!
header.h
main.cpp
template<typename T>
class manual_init {
public:
constexpr manual_init() = default;
void initialize() { storage_.initialize(); }
void destroy() { storage_.destroy(); }
T& get() { return storage_.get(); }
private:
storage<T> storage_;
};
template<auto& ... Init>
struct scoped_initializer {
scoped_initializer() {
Init.initialize();
...
}
~scoped_initializer() {
Init.destroy();
...
}
};
#include "header.h"
constinit manual_init<Global> global;
int main() {
scoped_initializer<global> initializer;
global->foo();
}
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.