5

Static initialization is well described subject, here and even at this site here

And everywhere it is written that problem will occurs if there exists different compilation units with related static variables. And if static variables exists in one compilation unit, there shouldn't be problem: they will be initialized in order of their position in file.

But I have this code:

template <typename T>
class A{
public:
    int _data;
    T _obj;
    A(int data) :_data(data){}
};

template <typename T>
class B{
public:
    const static B<T> nullObj;
    B(int data) :_a(new A<T>(data)){}
    A<T> *_a;
};

template <typename T>
class C{
public:
    const static C<T> nullObj;
    C() :_a(nullObj._a){}
    C(bool t) :_a(B<T>::nullObj._a){
        _a->_data++; //FAILS HERE!
    }
    A<T> *_a;
};

template <typename T>
const B<T> B<T>::nullObj(0);

template <typename T>
const C<T> C<T>::nullObj(false);

class _B{};
class  _A{ public: _A(){}; C<_B> g; };

int main(){
    return 0;
}

And it fails at runtime before entering main() function, because it tries to initialize const C<T> C<T>::nullObj(false); before const B<T> B<T>::nullObj(0); is initialized. Despite their position in one file.

Why does it tries to initialize second static variable before first static variable? Does there exist chapter in C++ Standart where described real situation with sequence of static initialization?

Community
  • 1
  • 1
Arkady
  • 2,084
  • 3
  • 27
  • 48
  • "Why does it tries to initialize second static variable before first static variable?" There's no "first" and no "second" variable here, it's a pair of templates from which you could produce zero, two, four, six, eight, etc. static variables on template instantiation. – Sergey Kalinichenko Aug 27 '14 at 11:26
  • @dasblinkenlight, ok, you are right, then question is: "why does it tries to initialize second static template before first one?" – Arkady Aug 27 '14 at 11:28

3 Answers3

5

C++ Standard, section 14.7.1 [temp.inst]:

Unless a member of a class template or a member template has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist; in particular, the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist


C<T> is instanciated before B<T>, so the order of initialization is well defined, and is :

1) const static C<T> nullObj;

2) const static B<T> nullObj;

Since C constructor dereference B<T>::nullObj._a, you have undefined behavior.

Solution:

You must use the static member B<_B>::nullObj somehow for it to be initialized, e.g. if you do :

class _B{};
class _A{ public: _A() : b(B<_B>::nullObj) {}; B<_B> b; C<_B> g; };

Then B<_B>::nullObj is indeed initialized before C constructor needs it

quantdev
  • 23,517
  • 5
  • 55
  • 88
  • I modified _A to initialize B<_B> before C<_B> this way: `class _A{ public: _A() : b(0) {}; B<_B> b; C<_B> g; };` still it fails. – Arkady Aug 27 '14 at 11:45
  • @Arkady see my edit, you are still not instantiating the static member – quantdev Aug 27 '14 at 12:02
  • Yes, that solves and explains everything, thank you! But there still is a question: why adding g(0) at constructor initialization list also solves problem? I.e. `b(0), g(0)` - also works. – Arkady Aug 27 '14 at 12:08
  • 1
    Because `g(0)` forces the initialization of `C::nullObj` (note that your previous version was implicitly calling `g()`) – quantdev Aug 27 '14 at 12:16
2

Template instances are not defined until they are instantiated. The first time C<> is instantiated is in _A's constructor. And B<> is instantiated by C<>, so B<_B> is defined when C<_B> is instantiated. Therefore C<_B> is before B<_B>.

You can add explicit template instantiation template class B<_B>; before the definition of _A, which I believe should fix the order.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • I checked it. If I change _A to `class _A{ public: _A() : b(0) {}; B<_B> b; C<_B> g; };` , so B<_B> now have to be initialized before C<_B>, there is still failing. – Arkady Aug 27 '14 at 11:36
  • Interestingly, it seems to run ok if I add `g(0)` to the initialization list. Either there's undefined behaviour or there's a rule which is unknown to me. All of these versions (your original and your new `_A`) work for me with the explicit instantiation (notice that I had a typo originally, it's supposed to be `B<_B>`). – eerorika Aug 27 '14 at 11:53
  • As I know, order of initialization of class members defined by their appearance in code. So, if `b` goes before `g` in class body, it have to be initialized first, even if in constructor g(0) will be before b(0). But yes, if I add g(0) after or before b(0), it works. I just wonder why. – Arkady Aug 27 '14 at 11:59
  • 1
    Yeah, the fact that it works with `g(0)` is that it only crashes with `C()` which uses `C<_B>::nullObj`. – eerorika Aug 27 '14 at 12:10
  • Thank you very much, you made this problem clean for me. – Arkady Aug 27 '14 at 12:23
1

The point is in _A's constructor. As cppreference states:

The default constructor for class T is trivial (i.e. performs no action) if all of the following is true:

  • The constructor is not user-provided (that is, implicitly-defined or defaulted)
  • T has no virtual member functions
  • T has no virtual base classes
  • T has no non-static members with brace-or-equal initializers. (since C++11)
  • Every direct base of T has a trivial default constructor
  • Every non-static member of class type has a trivial default constructor

This is not trivially constructible

class  _A{ public: _A(){} };

while this is

class  _A{ public: };

and in the case of the example this definitely hasn't a trivial constructor:

class  _A{ public: _A(){} C<_B> g; };

that means the constructor does perform initialization routines for the members of the class. As the standard says 14.7.1 [temp.inst]

the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist

and since the non-trivial constructor just provided us the use (not the need) for static variables to be initialized, the CRT begins its work and starts initializing

const static C<T> nullObj;

which in turn dereferences something unitialized and you have undefined behavior.

To solve, as quantdev as stated, either you initialize B<_B>::nullObj before this process or change the design of your code.

Marco A.
  • 43,032
  • 26
  • 132
  • 246