Reputation: 5206
Disclaimer: I come from a Java background, and, as such, I have no clue on how a lot of the internals of C++ (and associated libraries) work.
I have read enough to know that double-checked locking is evil, and a correct and safe implementation of a singleton pattern requires the appropriate tools.
I believe the following code may be unsafe, subject to compiler reordering and assignment of uninitialized objects, but I am not sure if I am missing something that I do not understand about the language.
typedef boost::shared_ptr<A> APtr;
APtr g_a;
boost::mutex g_a_mutex;
const APtr& A::instance()
{
if (!g_a)
{
boost::mutex::scoped_lock lock(g_a_mutex);
if (!g_a)
{
g_a = boost::make_shared<A>();
}
}
return g_a;
}
I believe the actual code is compiled under C++03.
Is this implementation unsafe? If so, how?
Upvotes: 5
Views: 340
Reputation: 62613
All of this is absolutelty unneccesary in modern C++. Singleton code should be as simple as this for anybody but dinosaurs:
A& A::instance() {
static A a;
return a;
}
100% thread safe in C++11 and up.
However, I have a mandatory statement to make: Singletons are evil antipattern and should be banned forever.
Yes, provided code is unsafe. Since the reading is done outside the mutex, it is totally possible that the modifications to g_a
itself would be visible, but not that modifications to the object g_a
is pointing to. As a result, the thread will bypass mutex locking and return a pointer to non-constructed object.
Upvotes: 1
Reputation: 157484
Yes, the double-checked locking pattern is unsafe in archaic languages. I can't do any better than the explanation in What's wrong with this fix for double checked locking?:
The problem apparently is the line assigning instance -- the compiler is free to allocate the object and then assign the pointer to it, OR to set the pointer to where it will be allocated, then allocate it.
If you were using C++11 std::shared_ptr
, you could use atomic_load
and atomic_store
on the shared pointer object, which are guaranteed to compose correctly with each other and with mutexes; but if you're using C++11 you may as well just use a dynamically-initialized static
variable, which is guaranteed to be thread-safe.
Upvotes: 3