views:

59

answers:

3

This is another variation of an old theme: The initialization order of static objects in different translation units is not defined.

Below is a stripped-down example of my particular szenario. The classes G and F are non-POD types. F depends on G is the sense that to construct an instance of F you need some number of instances of G. (For example, F could be some message an application emits, and instances of G would be components of such messages.)

G.hpp

#ifndef G_HPP
#define G_HPP

struct G
{
    G() {} // ...
};

inline G operator+(G, G) { return G(); }

#endif

Gs.hpp

#ifndef GS_HPP
#define GS_HPP

#include "G.hpp"

extern const G g1;
extern const G g2;
extern const G g3;
extern const G g4;
extern const G g5;
extern const G g6;
extern const G g7;
extern const G g8;
extern const G g9;

#endif

Gs.cpp

#include "Gs.hpp"

const G g1;
const G g2;
const G g3;
const G g4;
const G g5;
const G g6;
const G g7;
const G g8;
const G g9;

F.hpp

#ifndef F_HPP
#define F_HPP

#include "G.hpp"

struct F
{
    F(G) {} // ...
};

#endif

Fs.hpp

#ifndef FS_HPP
#define FS_HPP

#include "F.hpp"

extern const F f1;
extern const F f2;
extern const F f3;

#endif

Fs.cpp

#include "Fs.hpp"
#include "Gs.hpp"

const F f1(g1 + g2 + g3);
const F f2(g4 + g5 + g6);
const F f3(g7 + g8 + g9);

F's constructor takes an argument which is the result of applying operator+ to instances of G. Since the instances of both F and G are global variables, there is not guarantee that the instances of G have been initialized when the constructor of F needs them.

The particularity here is that there are many Gs and Fs all over the place, and I would like to keep the syntax as much as possibly close to the code posted above, while still enforcing the construction of a G whenever an F needs it.

A: 

Keep the extern declarations in the headers. Put all the fN and gN definitions into a single cpp file in the appropriate order.

wilx
Unfortunately, I have many fs and gs in various libraries, so I cannot put them in a single cpp file.
cj
A: 

Maybe a trick similar to the one used to initialize cin and friends' filebuffers would work for you? (Read <iostream> carefully.)

Zack
+1  A: 

From http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.15 .

Change your global objects into functions which construct the object on first use.

// Gs.hpp
const G & g1();

// Gs.cpp
const G & g1() {
  static const G* g1_ptr = new G();
  return *g1_ptr;
}

// Fs.cpp
const F & f1() {
  static const F* f1_ptr = new F(g1() + g2() + g3());
  return *f1_ptr;
}

Or if you really can't stand adding the extra ()s, use some #defines to hide them:

// Gs.hpp
const G & get_g1();
#define g1 (get_g1())
// Definition of get_g1() like g1() from prev. example
aschepler
Would be good the get rid of the `gN` function definition boilerplate, as well. The fs don't need to be functions as nothing else static depends on them.
cj
Yes, the fNs don't need this change to make this example safe. But I prefer to just protect all global variables in this way so I don't run into another initialization problem all over again when I add or change some other code.
aschepler
Instead of having N identical `get_gN` functons, you could use a template: `template <int N> G return g; }`. You could use these with either a plain `get_g<1>()` or using `#define G1 get_g<1>()`
Bart van Ingen Schenau