91 Advanced The Hundred
The Singleton That Died First
the singleton is lazy, thread-safe, and already gone
struct Logger {
bool open = true;
~Logger() { open = false; }
void write(const char* msg) {
std::cout << (open ? "[log] " : "(logger already closed) ") << msg << "\n";
}
};
Logger& logger() {
static Logger instance; // Meyers singleton: the classic init-order fix
return instance;
}
struct Connection {
Connection() { std::cout << "connection opened\n"; }
~Connection() { logger().write("connection closed"); }
};
Connection conn;
int main() { logger().write("app started"); }
Run it. Does the closing message reach the log?
Answer
No — the singleton is already gone. Typical output (GCC 11 on x86-64, -O0); rebuild with GCC
at -O2 and the last line quietly turns back into [log] connection closed:
Why¶
Statics are destroyed in reverse order of initialization, and a function-local static counts
as initialized where control first passes its declaration: conn before main starts,
instance inside it — last in, first dead. (GCC constant-initializes instance into .data,
but it is still destroyed as if it had been initialized right there.) ~Connection then calls
logger(), whose guard variable is never reset, so it hands back the corpse; reading open
from a dead object is undefined behavior. At -O0 the byte still holds the destructor's
false; from -O1 up GCC deletes that store, since nothing may legally read a member once the
destructor has returned, and the check stops catching (Clang 18 keeps it). Log from
Connection's constructor too and the bug vanishes, which is why it lands the day a log line
gets deleted. -Wall -Wextra say nothing; Clang's -Wexit-time-destructors names both statics.
The fix¶
Make the singleton immortal — leak it inside logger(), so the atexit queue never sees it:
static Logger* instance = new Logger(); // never destroyed, so never too early
return *instance; // Valgrind: "still reachable", not a leak
Takeaway: you can force initialization order; destruction order you can only outlive.
Try it: g++ -std=c++17 -Wall -Wextra main.cpp -o demo && ./demo — then again at -O2.