tags:

views:

74

answers:

4

I think my problem is best described in code:

#include <stdio.h>

struct Foo;

extern Foo globalFoo;

struct Foo {
    Foo() {
        printf("Foo::Foo()\n");
    }

    void add() {
        printf("Foo::add()\n");
    }

    static int addToGlobal() {
        printf("Foo::addToGlobal() START\n");

        globalFoo.add();

        printf("Foo::addToGlobal() END\n");

        return 0;
    }
};

Foo globalFoo;

int dummy = Foo::addToGlobal();

int main() {
    printf("main()\n");

    return 0;
}

The above prints (with gcc 4.4.3):

Foo::Foo()
Foo::addToGlobal() START
Foo::add()
Foo::addToGlobal() END
main()

This is what I expect, and seems logical.

However, when I swap the following lines:

Foo globalFoo;
int dummy = Foo::addToGlobal();

into this:

int dummy = Foo::addToGlobal();
Foo globalFoo;

the program outputs the following:

Foo::addToGlobal() START
Foo::add()
Foo::addToGlobal() END
Foo::Foo()
main()

It seems instance methods of Foo are being called using an instance which has not yet been constructed! Something as simple as moving the declaration of a variable in the global scope is affecting the behaviour of the program, and that leads me to believe (1) the order of initialization of globals is not defined and (2) the order of initialization of globals ignores all dependencies. Is this correct? Is it possible to make sure the constructor of Foo is called before initializing dummy?

The problem I am trying to solve is filling a repository of items (a static instance of Foo) statically. In my current attempt, I am using a macro which (among other things) creates a global variable (in an anonymous namespace to avoid name clashing) whose initialization triggers the static initialization. Perhaps I'm tackling my problem from the wrong angle? Is there a better alternative(s)? Thanks.

+6  A: 

(1) the order of initialization of globals is not defined

Global variables in a single translation unit (source file) are initialized in the order in which they are defined.

The order of initialization of global variables in different translation units is unspecified.

(2) the order of initialization of globals ignores all dependencies

Right.

Is it possible to make sure the constructor of Foo is called before initializing dummy?

Yes, if globalFoo is defined before dummy and they are in the same translation unit.

One option would be to have a static pointer to the global instance; such a pointer will be initialized to null before any dynamic initialization takes place; addToGlobal can then test whether the pointer is null; if it is, then it is the first time the global is being used and addToGlobal can create the global Foo.

James McNellis
By "Is it possible to make sure the constructor of Foo is called before initializing dummy?" I meant "other than changing the order of the definitions". Your answer makes things a bit clearer, but it doesn't really 'solve' my issue (yet).
strager
Seems to me like it would be preferable to have a static member of Foo which is the repository. Treating Foo as both a repository (i.e. globalFoo) and for other purposes does not 'feel right'. If on the other hand Foo 'has a' global repository then init should work OK and design seems cleaner.
Steve Townsend
I would rather have the singleton implemented as a function static: `Foo return foo; }` as there is a slightly better guarantee in the order of initialization: it will be initialized during the first call to the method. That will still have issues during finalization...
David Rodríguez - dribeas
@David - Finalization issues iff C++/CLI?
Steve Townsend
A: 

You're correct, initialization of globals between translation units is undefined. It is possible to get around this using the singleton pattern. However, be warned that this design pattern is often misused. Also be warned that order or destruction of globals is also undefined, in case you have dependencies in the destructors.

rlbond
The order of destruction is actually well-defined: objects with static storage duration are destroyed in the reverse order of when their initialization or construction was completed (there are a few nuances, but that's the general rule).
James McNellis
A: 

C++ lacks anything like Ada's pragma elaborate, so you can't make any assumptions whatsoever about the order initilizations will happen in. Sorry. It sucks, but that's the design.

T.E.D.
+1  A: 

On the order of initialization, read the answer here.

On how to solve the initialization issue, you can push the global to be a static local variable in a function. There standard guarantees that the static local variable will be initialized in the first call to the function:

class Foo {
public:
   static Foo& singleton() {
      static Foo instance;
      return instance;
   }
};

Then your other global variables would access the variable as:

Foo::singleton().add();

Note that this is not generally considered as a good design, and also that even if this solves the initialization issues, it does not solve the order of finalization, so you should be careful not to access the singleton after it has been destroyed.

David Rodríguez - dribeas
I came across this solution after posting my question. I tried it, and it seems to work. However, there may be a problem: will there be multiple instances due to `static` inside of `singleton`? That is, could `Foo::singleton()` possibly return different things if it is declared and defined in a header file and included across various translation units?
strager
Another potential problem with this pattern is that AFAIK it's not safe to access singleton concurrently.
FuleSnabel
@strager: When the local variable is declared `static` then there will be a single instance of it, even if the function is inlined (that is, compiled separatedly in different translation units) the linker must ensure that only a single definition of the variable exists.
David Rodríguez - dribeas
@FuleSnabel: During static initialization (the problem at hand) the application is still single threaded. If the `singleton` function is called by a single thread the first time, then the pattern is as thread safe as the object that it encapsulated. The problem with thread safety would arise if a second thread calls the function before the first call has completed initialization. If the object is initialized in a single thread this is thread safe.
David Rodríguez - dribeas