Article
Taming the Static-Initialization Order Fiasco
April 10, 2020
Three translation units, one silent failure.
// service.h
#include <string>
struct Service {
static const std::string NAME;
};
// service.cpp
#include "service.h"
const std::string Service::NAME = "SERVICE1";
// main.cpp
#include "service.h"
#include <iostream>
const std::string MSG = "service " + Service::NAME + " ready";
int main() {
std::cout << MSG << std::endl;
}Build succeeds, output isservice ready.Service::NAME was read before it was constructed: classic static-initialization order fiasco (SIOF).
Lazy but Safe
struct Service {
static const std::string& NAME() {
static const std::string _name = "SERVICE1";
return _name;
}
};
const std::string& MSG() {
static const std::string _msg =
"service " + Service::NAME() + " ready";
return _msg;
}
int main() {
std::cout << MSG() << std::endl;
}- Guaranteed correct order
- First-call latency and locking
- Resources released after
mainfinishes
Zero-Overhead Variant
Build the whole message at compile-time with a tiny header-only helper.
// static_string.h (C++11)
template <int... I> struct seq {};
template <int N, int... I> struct gen : gen<N-1,N-1,I...> {};
template <int... I> struct gen<0,I...> { typedef seq<I...> type; };
template <int N, typename Tag> struct sstr;
struct ref_tag {}; struct arr_tag {};
template <int N>
struct sstr<N,ref_tag> {
const char (&p)[N+1];
constexpr char operator[](int i) const { return p[i]; }
};
template <int N>
struct sstr<N,arr_tag> {
char p[N+1];
template <int N1,int... A,int... B>
constexpr sstr(const sstr<N1,ref_tag>& a,
const sstr<N-N1,ref_tag>& b,
seq<A...>,seq<B...>)
: p{a[A]...,b[B]...,0} {}
constexpr char operator[](int i) const { return p[i]; }
constexpr const char* c_str() const { return p; }
};
template <int N> constexpr auto lit(const char (&a)[N])
{ return sstr<N-1,ref_tag>{a}; }
template <int N1,int N2>
constexpr auto operator+(const sstr<N1,ref_tag>& a,
const sstr<N2,ref_tag>& b)
{ return sstr<N1+N2,arr_tag>{a,b,
typename gen<N1>::type{},typename gen<N2>::type{}}; }
Use it as a drop-in compile-time string builder.
#include "static_string.h"
#include <iostream>
constexpr auto NAME = lit("SERVICE1");
constexpr auto MSG = lit("service ") + NAME + lit(" ready");
int main() {
std::cout << MSG.c_str() << std::endl; // fully static, no SIOF
}C++17 Quality-of-Life
template <int N>
sstr(const char (&)[N]) -> sstr<N-1,ref_tag>; // CTAD
constexpr auto NAME = sstr{"SERVICE1"}; // size deduced
constexpr auto MSG = sstr{"service "} + NAME + sstr{" ready"};
Checklist
- Avoid globals that depend on other globals.
- If you must: wrap in a function-local static.
- Prefer constexpr objects; the compiler enforces safety.
- For strings, a tiny
static_stringlibrary yields zero-cost correctness even in C++11.