Эмуляция статических конструкторов для шаблонных классов

Я хотел бы иметь шаблонный класс со статическим членом данных и инициализировать его, эмулируя «статический конструктор». Для класса без шаблонов на этот вопрос уже дан ответ (см. статические конструкторы в C ++? Мне нужно инициализировать частные статические объекты а также Что такое статический конструктор?). Тем не менее, ни один из ответов, похоже, не работает для шаблонного класса.

Ниже приведен пример, который пытается адаптировать идиому «статический конструктор» из предыдущих ответов к шаблонному классу. (Обратите внимание, что пример просто инициализирует int и может быть написано без таких конструкторов; однако мне нужно общее решение.)

#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;
}

Это выводит:

Initializing Foo...
1
0

Но я ожидал, что это выведет:

Initializing Foo...
Initializing Bar...
1
1

Это пример «статического фиаско порядка инициализации?»

2

Решение

Нет, это не статический порядок инициализации фиаско. Это просто результат того факта, что каждый член класса шаблона сам по себе является шаблоном и как таковой не создается до момента его использования.

Ваш код никогда не использует init член, так init никогда не создается.

Тем не менее, ваша проблема легко решается:

#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;
}

Это дает вам то, что вы хотите проще.

2

Другие решения

Вы должны явно создать экземпляр 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;
}

Причина в том, что Bar<1>::x не зависит от Bar<1>::initializer, Таким образом, компилятор не создает его, поскольку вы его не используете. Actualy, initializer инициализация не инициализирует x, x первый инициализированный ноль, тогда если initializer инстанцируется, x является назначенный новое значение.

Нет риска статического провала инициализации до тех пор, пока initializer создается в той же единице перевода, что и та, где x является инстанцирован. Так что это, безусловно, хорошая идея, чтобы явно создать экземпляр x тоже.


В качестве альтернативы вы можете объявить эти переменные как статические локальные:

#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;
}

Но если инициализация не тривиальна, сгенерированный код внутри x() может быть недостаточно оптимизирован.


В зависимости от вашей проблемы, вы также можете определить x как оболочку для 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;
[...];

2

Я нашел чистое решение, которое работает для любого типа данных. Поскольку операция присваивания внутри template оценивается, когда компилятор сталкивается с конкретным Bar<N>::x чтобы создать экземпляр, мы можем написать:

template<int N>
int Bar<N>::x = init<N>();

где init() это функция на основе N который возвращает int, Дополнительно, init() будет вызываться только один раз для каждого значения N который создает экземпляр компилятор.

В качестве более полезного примера здесь я инициализирую статический std::array согласно некоторой произвольной функции:

#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;
}
0
По вопросам рекламы [email protected]