4

I am using something like the following sections of code to do some initialization. I know that the initialization of p<T>::i_ is unordered. I believe that h is ordered, so I should be able to reason about the order that it is initialized in. Given that the header for p is included before the definition of h, is there any guarantee that p<T>::i_ will be initialized before h?

struct helper
{
   template <typename T>
   helper(const T&, int i)
   {
      p<T>::i_::push_back(i);
   }
};
static helper h;

The class p is defined below.

template <typename T>
struct p
{
   static std::vector<int> i_;
};
template <typename T>
std::vector<int> p<T>::i_;
Chad
  • 18,706
  • 4
  • 46
  • 63
Graznarak
  • 3,626
  • 4
  • 28
  • 47

3 Answers3

6

The order of initialization of objects with static storage duration is undefined across translation units, and sequential within each translation unit.

In your particular case things are more complicated, since one of the objects with static storage is a static member of a template class. What that means in a practical way is that each translation unit that accesses the member p<T>::i_ will create the symbol, and add the appropriate initialization code. Later the linker will pick one of the instances and keep it. Even if it looks like p<T>::i_ is defined before h in your translation unit, you don't know which instance of p<T>::i_ will be kept by the linker, and that might be one in a different translation unit and thus the order is not guaranteed.

In general it is a bad idea to have global objects, I would suggest that you try to redesign your program without those globals.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • +1 For mentioning translation units/linker involvement. I was wondering if `static` non-template variable in template gets initialized when template is instantiated? Or is it initialized regardless whether template gets instantiated or not (seems odd a bit)? – lapk Aug 27 '13 at 20:12
  • What's important is that implicit instantiation of a template *does not* trigger initialization of static members. They're initialized when they're used in a way that requires a definition. – jrok Aug 27 '13 at 20:14
  • @PetrBudnik: Unless you explicitly instantiate a template, all of the members are instantiated on demand. If the program does not use a static member of the template, the member is not instantiated by the compiler unless it is used. – David Rodríguez - dribeas Aug 27 '13 at 20:17
  • Right. That's what I am trying to understand. Maybe I worded it wrongly. Will not `p` be instantiated during instantiation of `h::helper()` and then `p::i_` will be initialized?.. Probably, I should just try it. – lapk Aug 27 '13 at 20:29
  • re order of initialization across translation units, I believe calling a function in a translation unit guarantees initialization of the static-duration objects in it before the function is entered, so a simple precaution in function-call initializers makes life easy. (though you can make impossible sequencing demands if you try hard enough, initialization of a in A calls x() in B which has a global whose initialization calls y() in A that uses a value initialiized after A's a is the simplest of the pathological ones) – jthill Aug 27 '13 at 20:33
  • 1
    @PetrBudnik: Yes, the call inside the constructor call for `h` will cause the instantiation of `p::i_`, but that need not be the *only* instantiation of `p::i_`. If a different translation unit contained a type `helper2` clone of `helper`, and it created a variable `h2` with static storage duration that triggered the instantiation of `p::i_`, both translation units would instantiate it. The linker will drop one of them. For the translation unit that keeps its own instance, the order is guaranteed, but there is no guarantee for the other [...] – David Rodríguez - dribeas Aug 27 '13 at 20:38
  • 1
    [...], that is it would drop the version in `helper` and the final order could be `h > p::i_ > h2`. During initialization of `h`, the yet uninitialized `p::i_` would be accessed causing undefined behavior. – David Rodríguez - dribeas Aug 27 '13 at 20:40
  • @jthill: There is no such guarantee. – David Rodríguez - dribeas Aug 27 '13 at 20:42
  • @DavidRodríguez-dribeas Ok, now that you chewed it for me it's perfectly clear. I needed the bit about "helper2 clone with the same type". I wish I can +1 once more. Thanks! – lapk Aug 27 '13 at 20:43
  • @DavidRodríguez-dribeas So I've been misreading "[initialization] shall occur before the first odr-use (3.2) of any function or variable defined in the same translation unit as the variable to be initialized" (from 3.6.2p4, and see its example), but I can't see how. Help, please? – jthill Aug 27 '13 at 21:18
  • @jthill: The beginning of that sentence (3.6.2/4) is *If the initialization is deferred to some point in time after the first statement of `main`*. The quote is limiting how much the initialization can be deferred, but that offers no guarantee as of the order of initialization of different variables with static storage. It would be possible to enforce. Consider 2 translation units that defined respectively functions `fa` and `fb` and variables `a` and `b` initialized as: `T a = fb();` and `U b = fa();` – David Rodríguez - dribeas Aug 27 '13 at 21:33
  • Okay, I see it now -- it even mentions that explicitly in the middle of the paragraph at the end of the example. During startup, the only thing you can rely on is the "before any other initialization" zero-initialization and in-module ordering. Thanks for the help. – jthill Aug 27 '13 at 21:43
5

Objects at global or namespace scope are constructed top to bottom within one translation unit. The order of construction of global or namespace level between different translation units is not defined. The most reasonable way to order initialization between translation units it to wrap objects within suitable accessor functions, e.g.:

template <typename T>
something<T>& get() {
    static something<T> values;
    return value;
}

Note, however, that this isn't thread-safe in C++03 (as C++03 has no concepts of threads anyway). It is thread-safe in C++11.

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

No, it's not guaranteed.

What you can do is however:

template<typename T>
std::vector<int>& registry() {
    static std::vector<int> reg;
    return reg;
}

...
registry<T>().push_back(i);
...

Even better is to avoid doing things that are too smart during startup.

Debugging before the start or after the end of main is a true nightmare (and IMO not even covered 100% in the standard). Simple registration is may be ok, but don't ever do anything that can possibly fail.

Over the years I moved away from this approach to explicit initialization/shutdown and never looked back.

6502
  • 112,025
  • 15
  • 165
  • 265
  • Debugging dynamic initialization may not be as hard as you think. There is a platform-dependant symbol you can break on that marks the entry to dynamic initialization, and then you can step through it just like any function. – Andrew Tomazos Aug 27 '13 at 21:49
  • @user1131467: I've seen that debugging tools don't work as expected during initialization and during shutdown. Note that during initialization it's not even clear how much of the standard library has been already initialized and can be used and how much of the standard library has been already shut down during destruction of a static-duration instance. Add to this that for example windows in certain cases just silently swallows segfaults when quitting an application... – 6502 Aug 28 '13 at 06:57