4

I'm using Windows XP / Visual C++ 2008.

I've run into a C++ static initialization order problem, which I've solved with the well-known "construct on first use" idiom:

Foo foo; // Forget this

Foo &foo() // Do this instead
{
   // Use ptr, not reference, to avoid destruction order problems
   static Foo *ptr = new Foo();
   return *ptr;
}

However, I've been searching around and it appears that Windows (my platform) does not guarantee thread-safety of local statics, though it does make this guarantee for global statics.

So, if I make my object global, I get thread safety but I have initialization order problems. If I use "construct on first use", I avoid initialization order problems but I get race conditions. How can I solve both problems simultaneously?

Charles
  • 50,943
  • 13
  • 104
  • 142
Dave
  • 1,519
  • 2
  • 18
  • 39
  • 4
    By the way you can avoid using `new` (which you should when you can) by doing `static Foo foo; return foo;` – Seth Carnegie Feb 09 '12 at 00:41
  • If you could upgrade to at least Vista, you can use One-Time Initialization: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363808(v=vs.85).aspx. – MSN Feb 09 '12 at 00:42
  • @SethCarnegie, that still isn't thread safe. The MSVC implementation has an implicit bool to mark whether the static was initialized which is not synchronized. – MSN Feb 09 '12 at 00:43
  • 2
    @MSN I didn't say it was, I just said that he should avoid heap allocation when possible – Seth Carnegie Feb 09 '12 at 00:44
  • 1
    Just checking: Are you actually writing a multi-threaded program? If not, don't don't need to worry about thread safety. Or, one solution is to get your main thread to create all of the global objects you want before any other threads get created. – Scott Langham Feb 09 '12 at 01:09
  • 2
    And to add what Scott was saying, you can get `main` to initialise the objects by having it call all the functions that return a `static` variable once before creating the threads. – Seth Carnegie Feb 09 '12 at 01:15
  • You can protect on-demand initialization with mutex or critical section. – SigTerm Feb 09 '12 at 01:39
  • I am modifying a general-purpose library, so I must assume multiple threads, even before main() starts. I do not use static Foo foo; because it introduces order-of-destruction problems. – Dave Feb 09 '12 at 03:57

3 Answers3

7

In C++ 2011 you use std::call_once():

#include <mutex>
void initialize(Foo*& ptr)
{
    ptr = new Foo();
}
std::once_flag flag
Foo& foo()
{
    static Foo* ptr(0);
    std::call_once(flag, initialize, std::ref(ptr));
    return *ptr;
}

If you can't use C++2011 you might want to look at system's underlying facilities instead. For POSIX this would be pthread_once(). How this is done on other platform I don't know.

That said, I recommend not to use this because it is essentially some form of global data and there is generally no good reason to use this. There are exceptions but they are rare. Extremely rare, actually.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
0

As you're on windows you can use a critical section. This only allows one thread to be running in the section of code after 'lock' is constructed and before it destructs - which is after foo has been initialized and before the function returns.

#include <atlbase.h>

CComAutoCriticalSection fooCs;

Foo& GetFoo()
{
   CComCritSecLock<CComAutoCriticalSection> lock(cs);
   static Foo foo;
   return foo;
}
Scott Langham
  • 58,735
  • 39
  • 131
  • 204
  • Now there's an order-of-initialization problem with fooCs. – Dave Feb 09 '12 at 03:54
  • 1
    If fooCs is a file static, there is no order-of-initialization problem. In any case, use DCLP (www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf). – mcmcc Feb 09 '12 at 04:36
-2

Don't use global variables, nor singletons, and you won't have these problems.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 1
    Good answer. But you can add more description on how to do things correctly. – Martin York Feb 09 '12 at 00:49
  • The global object I'm creating is for stream output. It is appropriately global. – Dave Feb 09 '12 at 03:55
  • If you're sure it needs to be global, and it relates to iostreams, maybe you should do what iostreams themselves do, which is discussed briefly here: http://stackoverflow.com/questions/3780898/how-are-iostream-objects-cin-cout-cerr-and-clog-implemented . Or just look at the implementation on your particular platform for std::cout and friends--and do what they do. – John Zwinck Feb 09 '12 at 09:16