There are several possibilities for global state (whether mutable or not).
If you fear that you'll have an initialization issue, then you should use the local static
approach to create your instance.
Note that the clunky singleton design you present is not mandatory design:
class Singleton
{
public:
static void DoSomething(int i)
{
Singleton& s = Instance();
// do something with i
}
private:
Singleton() {}
~Singleton() {}
static Singleton& Instance()
{
static Singleton S; // no dynamic allocation, it's unnecessary
return S;
}
};
// Invocation
Singleton::DoSomething(i);
Another design is somewhat similar, though I much prefer it because it makes transition to a non-global design much easier.
class Monoid
{
public:
Monoid()
{
static State S;
state = &s;
}
void doSomething(int i)
{
state->count += i;
}
private:
struct State
{
int count;
};
State* state;
};
// Use
Monoid m;
m.doSomething(1);
The net advantage here is that the "global-ness" of the state is hidden, it's an implementation details that clients need not worry about. Very useful for caches.
Let us, will you, question the design:
- do you actually need to enforce the singularity ?
- do you actually need the object be built before
main
starts ?
Singularity is generally over-emphasized. C++0x will help here, but even then, technically enforcing singularity rather than relying on programmers to behave themselves can be very annoying... for example when writing tests: do you really want to unload/reload your program between each unit test just to change the configuration between each one ? Ugh. Much more simple to instantiate it once and have faith in your fellow programmers... or the functional tests ;)
The second question is more technical, than functional. If you do need the configuration before the entry point of your program, then you can simply read it when it starts.
It may sound naive, but there is actually one issue with computing during the library load: how do you handle errors ? If you throw, the library is not loaded. If you do not throw and go on, you are in an invalid state. Not so funny, is it ? Things are much simpler once the real work has begun, because you can use the regular control-flow logic.
And if you think about testing whether the state is valid or not... why not simply building everything at the point where you'd test ?
Finally, the very issue with global
is the hidden dependencies that are introduced. It's much better when dependencies are implicit to reason about the flow of execution, or the impacts of a refactoring.
EDIT:
Regarding initialization order issues: objects within a single translation unit are guaranteed to be initialized in the order they are defined.
Therefore, the following code is valid according to the standard:
static int foo() { return std::numeric_limits<int>::max() / 2; }
static int bar(int c) { return c*2; }
static int const x = foo();
static int const y = bar(x);
The initialization order is only an issue when referencing constants / variables defined in another translation unit. As such, static
objects can naturally be expressed without issues as long as they only refer to static
objects within the same translation unit.
Regarding the space issue: the as-if
rule can do wonders here. Informally the as-if
rule means that you specify a behavior and leave it up to the compiler/linker/runtime to provide it, without a care in the world for how it is provided. This is what actually enables optimizations.
Therefore, if the compiler chain can infer that the address of a constant is never taken, it may elide the constant altogether. If it can infer that several constants will always be equal, and once again that their address are never inspected, it may merge them together.