I have a function foo()
protected by a mutex m
which is defined as a local static variable of foo()
. I'd like to know whether it's safe to call foo()
in the destructor of an object bar
with static storage duration:
// foo.h
void foo();
// foo.cpp
#include "foo.h"
#include <mutex>
void foo() {
static std::mutex m;
std::lock_guard<std::mutex> lock(m);
// ...
}
// bar.h
struct Bar { ~Bar(); };
extern Bar bar;
// bar.cpp
#include "bar.h"
#include "foo.h"
Bar::~Bar() { foo(); }
Bar bar;
// main.cpp
int main() {
Bar bar;
return 0;
}
If std::mutex
is trivially destructible, this should be safe, because bar
will be destructed before m
. On GCC 5.4, Ubuntu 16.04, calling std::is_trivially_destructible<std::mutex>::value
returns true
, so it seems okay at least in this compiler. Any definitive answer?
Related: Google C++ Style Guide on Static and Global Variables
Edit
Apparently, I wasn't clear enough and should have provided more context. Yes, the underlying problem is that I want bar
to be destructed before m
. This is the "destruction" part of the well known "static initialization fiasco problem", see for example:
https://isocpp.org/wiki/faq/ctors#construct-on-first-use-v2
The point is simple: if there are any other static objects whose destructors might use ans after ans is destructed, bang, you’re dead. If the constructors of a, b and c use ans, you should normally be okay since the runtime system will, during static deinitialization, destruct ans after the last of those three objects is destructed. However if a and/or b and/or c fail to use ans in their constructors and/or if any code anywhere gets the address of ans and hands it to some other static object, all bets are off and you have to be very, very careful.
This is why Google recommends against using static objects, unless they are trivially destructible. The thing is, if the object is trivially destructible, then the order of destruction doesn't really matter. Even if m
is "destructed" before bar
, you can still in practice use m
in the destructor of bar
without crashing the program, because the destructor effectively does nothing (it doesn't deallocate any memory or release any other type of resources).
And in fact, if m
is trivially destructible, then the program may not even destruct m
at all, which actually ensures that m
is "destructed" after bar
or any other static objets which are not trivially destructible. See for instance:
http://en.cppreference.com/w/cpp/language/lifetime#Storage_reuse
A program is not required to call the destructor of an object to end its lifetime if the object is trivially-destructible or if the program does not rely on the side effects of the destructor.
For these reasons, it is in practice overkill to use complex singleton idioms such as the Nifty Counter idiom if your singleton is trivially destructible.
In other words, if std::mutex
is trivially destructible, then my code sample above is safe: m
is either destructed after bar
, or it is "technically destructed" before bar
but wouldn't cause a crash anyway. However, if std::mutex
is not trivially destructible, then I may need to use the Nifty Counter idiom instead, or alternatively the simpler but "purposefully leaking" Trusty Leaking idiom.
Related: