问题
I would like to have a templated class with a static data member, and initialize it by emulating a "static constructor." For a non-templated class, this has already been answered (see static constructors in C++? I need to initialize private static objects and What is a static constructor?). However, none of the answers seem to work for a templated class.
The following is an example that tries to adapt the "static constructor" idiom from the previous answers to a templated class. (Note that the example is simply initializing an int
and could be written without such constructors; however, I require a general solution.)
#include <iostream>
struct Foo
{
static int x;
static struct init
{
init()
{
std::cout << "Initializing Foo..." << std::endl;
x = 1;
}
} initializer;
};
int Foo::x;
Foo::init Foo::initializer;
template<int N>
struct Bar
{
static int x;
static struct init
{
init()
{
std::cout << "Initializing Bar..." << std::endl;
x = N;
}
} initializer;
};
template<int N>
int Bar<N>::x;
template<int N>
typename Bar<N>::init Bar<N>::initializer;
int main()
{
std::cout << Foo::x << std::endl;
std::cout << Bar<1>::x << std::endl;
return 0;
}
This outputs:
Initializing Foo...
1
0
But I expected it to output:
Initializing Foo...
Initializing Bar...
1
1
Is this an example of the "static initialization order fiasco?"
回答1:
No, it is not static initialization order fiasco. It is simply a result of the fact that every member of a template class is a template on it's own, and as such is not instantiated until used.
Your code never uses init
member, so init
is never instantiated.
However, your problem is easily solved:
#include <iostream>
template<int N>
struct Bar
{
static int x;
};
template<int N>
int Bar<N>::x= N;
int main()
{
std::cout << Bar<1>::x << std::endl;
return 0;
}
This gives you what you want in a simpler way.
回答2:
You need to explictly instantiate initializer
:
[...]
template<int N>
typename Bar<N>::init Bar<N>::initializer;
template
typename Bar<1>::init Bar<1>::initializer;
int main()
{
std::cout << Foo::x << std::endl;
std::cout << Bar<1>::x << std::endl;
return 0;
}
The reason is that Bar<1>::x
does not depends on Bar<1>::initializer
. So the compiler does not instantiate it as you do not use it. Actualy, initializer
initialization does not initialize x
. x
is first zero initialized, then if initializer
is instantiated, x
is assigned a new value.
There are no risk of static initialization fiasco as long as initializer
is instantiated in the same translation unit as the one where x
is instantiated. So it is certainly a good idea to explictly instantiate x
too.
Alternatively you could declare these variables as static locals:
#include <iostream>
template<int N>
struct Bar
{
static int x()
{
static int x_val;
static struct init
{
init()
{
std::cout << "Initializing Bar..." << std::endl;
x_val = N;
}
} initializer;//indeed this circumvolution is no more needed.
return x_val;
}
};
int main(){
std::cout << Bar<1>::x() << std::endl;
}
But if the initialization is not trivial, the generated code inside x()
may be under optimized.
Depending on your problem, you could also define x as a wrapper around an int:
class int_inited{
int val;
public:
int_inited(){
std::cout << "Perfoming initialization" << std::endl;
val=42;
}
operator int&(){
return val;
}
operator const int &() const{
return val;
}
};
template<class N>
struct Bar{
static int_inited x;
[...];
回答3:
I have found a clean solution that works for any data type. Since the assignment operation inside a template
is evaluated when the compiler comes across a specific Bar<N>::x
to instantiate, we can write:
template<int N>
int Bar<N>::x = init<N>();
where init()
is a function templated on N
that returns an int
. Additionally, init()
will only be called once for each value of N
that the compiler instantiates.
As a more useful example, here I initialize a static std::array
according to some arbitrary function:
#include <iostream>
#include <array>
template<int N>
struct Foo
{
static std::array<double,N> x;
};
template<int N>
std::array<double,N> init()
{
std::array<double,N> y;
for (int i=0; i<N; ++i) {
y[i] = (double)(i*i+i)/N;
}
return y;
}
template<int N>
std::array<double,N> Foo<N>::x = init<N>();
int main()
{
const int N = 10;
for (int i=0; i<N; ++i) {
std::cout << Foo<N>::x[i] << std::endl;
}
return 0;
}
来源:https://stackoverflow.com/questions/47703975/emulating-static-constructors-for-templated-classes