It's far from perfect, but you can always do what the implementations of
iostream do to ensure the initialization of std::cin
and std::cout
.
(This is sometimes known as the nifty counter idiom, or the Schwarz
counter, after the inventer of the technique.) There are several
variants, but the basic idea depends on the fact that order of
initialization is guaranteed within a single translation unit, so if you
define a (static) instance of some special type in your header, it will
be (normally, since headers are included at the top) constructed before
anything in the source source file. The constructor of this static
instance checks a global flag or counter; if the value is 0, it
initializes your global object, and increments the counter so that
following constructors won't initialize it. (There's no order of
initialization problem for the counter, because it depends on zero
initialization.) The only problem is how to declare the object itself.
I think in the earliest versions, it was declared in assembler, as an
array of enough bytes. What I've found to work (although not guaranteed
by the standard) is to declare a special, no-op constructor, and invoke
that in the "initialization" of the variable, And of course, the
initialization objects use placement new.
This may be clearer with a quick example:
Foo.hh:
class Foo
{
enum Hidden { noop };
Foo( Hidden ) {}
public:
Foo(); // The real constructor.
static Foo bar;
class FoobarInitializer
{
public:
FoobarInitializer();
}
};
static FoobarInitializer initializeFoobar;
Foo.cc:
namespace {
int initCount;
}
Foo Foo::bar( Foo::noop );
Foo::FoobarInitializer::FoobarInitializer()
{
if ( initCount == 0 ) {
new (&Foo::bar) Foo();
}
++ initCount;
}
This technique works equally well if bar
isn't a member of Foo
, but
you'll need to make more things public. (The initializer could be a
friend, but at the least, Foo::noop
must be public.)
I'll repeat that this is not guaranteed: Foo::Foo( noop )
may be
called on bar
after the initialization class has constructed it, and
an implementation is allowed to scribble over the memory before entering
the body of the constructor. But it's always worked in practice for me,
and I've used it with a number of different compilers.