Initialization order of static data inside class t

2019-01-15 20:51发布

问题:

// File: InitFirst.h

#pragma once

template <int val>
struct InitFirst
{
    static float s_dividedByThree;
};

template <int val>
float InitFirst<val>::s_dividedByThree = val / 3.0;

// File: Test.h

#include <conio.h>
#include <tchar.h>

#include "InitFirst.h"

float g_shouldBeOneThird = InitFirst<1>::s_dividedByThree;

int _tmain(int argc, _TCHAR* argv[])
{
    _cprintf("%f\n", g_shouldBeOneThird);
    getch();
    return 0;
}

Is g_shouldBeOneThird guaranteed to be initialized to around 0.333? In other words, is the statically initialized InitFirst<1>::s_dividedByThree guaranteed to be initialized by the time it's used for statically initializing g_shouldBeOneThird?

回答1:

From the standard (3.6.2):

Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. A reference with static storage duration and an object of POD type with static storage duration can be initialized with a constant expression (5.19); this is called constant initialization. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. Dynamic initialization of an object is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.

In your case here, since you're initializing float InitFirst<val>::s_dividedByThree with a constant expression, this will happen before any dynamic initialization (f.x float g_shouldBeOneThird). Though I have a feeling this simplified example might be a simplification of a case where you have dynamic initialization, then the relevant part is: "Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit.".

There is a trick to make sure that global variables (sort of) are initialised by the time you use them. The trick is to keep them as a static local variable in a global function. Since local static variables are initialised the first time they're accessed, initialisation order is not an issue anymore:

template <int val>
struct InitFirst
{
    static float & s_dividedByThree();
};

template <int val>
float & InitFirst<val>::s_dividedByThree(){
    static float staticVariable = val / 3.0;
    return staticVariable;
}

You can then access those variables almost as before:

float g_shouldBeOneThird = InitFirst<1>::s_dividedByThree();

Beware though, initialization of local static variables are not safe under multiple threads (it's not in the standard that they should be safe). If that is a concern for you, you might want to protect the initializations with some locks. The compilers are of course allowed to generate safe code, which is what gcc does by default (probably others too).