У меня есть класс с некоторыми статическими членами, и я хочу запустить некоторый код для их инициализации (предположим, этот код нельзя преобразовать в простое выражение). На Java я бы просто сделал
class MyClass {
static int myDatum;
static {
/* do some computation which sets myDatum */
}
}
Если я не ошибаюсь, C ++ не допускает такие статические блоки кода, верно? Что я должен делать вместо этого?
Я хотел бы получить решение для обоих следующих вариантов:
Для второго варианта я думал о:
class StaticInitialized {
static bool staticsInitialized = false;
virtual void initializeStatics();
StaticInitialized() {
if (!staticsInitialized) {
initializeStatics();
staticsInitialized = true;
}
}
};
class MyClass : private StaticInitialized {
static int myDatum;
void initializeStatics() {
/* computation which sets myDatum */
}
};
но это невозможно, поскольку C ++ (на данный момент?) не позволяет инициализировать неконстантные статические члены. Но, по крайней мере, это сводит проблему статического блока к проблеме статической инициализации выражением …
Для # 1, если вам действительно нужно инициализировать при запуске процесса / загрузке библиотеки, вам придется использовать что-то платформенное (например, DllMain в Windows).
Однако, если вам достаточно запустить инициализацию перед выполнением какого-либо кода из того же файла .cpp, что и статика, должно работать следующее:
// Header:
class MyClass
{
static int myDatum;
static int initDatum();
};
// .cpp file:
int MyClass::myDatum = MyClass::initDatum();
Сюда, initDatum()
гарантированно вызывается перед любым кодом из этого .cpp
файл выполнен.
Если вы не хотите загрязнять определение класса, вы также можете использовать лямбда (C ++, 11):
// Header:
class MyClass
{
static int myDatum;
};
// .cpp file:
int MyClass::myDatum = []() -> int { /*any code here*/ return /*something*/; }();
Не забывайте последнюю пару скобок — это на самом деле называется лямбда.
Что касается # 2, есть одна проблема: вы не можете вызвать виртуальную функцию в конструкторе. Вам лучше делать это вручную в классе, а не использовать для него базовый класс:
class MyClass
{
static int myDatum;
MyClass() {
static bool onlyOnce = []() -> bool {
MyClass::myDatum = /*whatever*/;
return true;
}
}
};
Предполагая, что у класса есть только один конструктор, он будет работать нормально; это потокобезопасно, так как C ++ 11 гарантирует такую безопасность для инициализации статических локальных переменных.
Оказывается, мы можем реализовать статический блок в стиле Java, хотя и вне класса, а не внутри него, то есть в области видимости блока перевода. Реализация немного уродливая под капотом, но при использовании она довольно элегантна!
Если вы напишите:
static_block {
std::cout << "Hello static block world!\n";
}
этот код будет выполняться до вашего main()
, И вы можете инициализировать статические переменные или делать все что угодно. Таким образом, вы можете разместить такой блок в своем классе .cpp
файл реализации.
Заметки:
В реализации статического блока используется фиктивная переменная, которая статически инициализируется с помощью функции. Ваш статический блок на самом деле является телом этой функции. Чтобы гарантировать, что мы не столкнемся с какой-либо другой фиктивной переменной (например, из другого статического блока — или где-либо еще), нам нужно немного макро-машин.
#define CONCATENATE(s1, s2) s1##s2
#define EXPAND_THEN_CONCATENATE(s1, s2) CONCATENATE(s1, s2)
#ifdef __COUNTER__
#define UNIQUE_IDENTIFIER(prefix) EXPAND_THEN_CONCATENATE(prefix, __COUNTER__)
#else
#define UNIQUE_IDENTIFIER(prefix) EXPAND_THEN_CONCATENATE(prefix, __LINE__)
#endif // __COUNTER__
и вот макрос работы по объединению вещей:
#define static_block STATIC_BLOCK_IMPL1(UNIQUE_IDENTIFIER(_static_block_))
#define STATIC_BLOCK_IMPL1(prefix) \
STATIC_BLOCK_IMPL2(CONCATENATE(prefix,_fn),CONCATENATE(prefix,_var))
#define STATIC_BLOCK_IMPL2(function_name,var_name) \
static void function_name(); \
static int var_name __attribute((unused)) = (function_name(), 0) ; \
static void function_name()
Заметки:
__COUNTER__
— это не является частью стандарта C ++; в этих случаях код выше использует __LINE__
, который тоже работает. GCC и Clang поддерживают __COUNTER__
,__attribute ((unused))
может быть сброшен или заменен [[unused]]
если у вас есть компилятор C ++ 11, которому не нравится неиспользуемое расширение в стиле GCC.main()
Вы не гарантированы, когда именно это происходит относительно других статических инициализаций.Вы Можно инициализировать статические члены данных в C ++:
#include "Bar.h"
Bar make_a_bar();
struct Foo
{
static Bar bar;
};
Bar Foo::bar = make_a_bar();
Возможно, вам придется подумать о зависимостях между переводами, но это общий подход.
Вот хороший способ подражать static
блок с использованием C ++ 11:
#define CONCATE_(X,Y) X##Y
#define CONCATE(X,Y) CONCATE_(X,Y)
#define UNIQUE(NAME) CONCATE(NAME, __LINE__)
struct Static_
{
template<typename T> Static_ (T only_once) { only_once(); }
~Static_ () {} // to counter "warning: unused variable"};
// `UNIQUE` macro required if we expect multiple `static` blocks in function
#define STATIC static Static_ UNIQUE(block) = [&]() -> void
void foo ()
{
std::cout << "foo()\n";
STATIC
{
std::cout << "Executes only once\n";
};
}
Причина кроется в совершенно другой природе кода, сгенерированного из C ++: среда выполнения не «управляемая». В сгенерированном коде после компиляции существует нет понятия больше «класса», и нет такой вещи как сущности кода, загружаемые по требованию «загрузчиком классов».
Есть некоторые элементы с примерно сопоставимым поведением, но вам действительно нужно понять их природу именно для того, чтобы использовать это поведение.
*.cpp
файл). Но вы не должны предполагать ничего сверх этого; особенно Вы никогда не можете быть уверены, если и когда эта инициализация действительно выполняется. Это предупреждение по-настоящему. Особенно не принимайте ничего о побочных эффектах такого кода инициализации. Для компилятора совершенно законно заменить такой код чем-то, что компилятор считает «эквивалентным». Можно предположить, что будущие версии компиляторов станут более умными в этом отношении. Может показаться, что ваш код работает, но может сломаться с разными флагами оптимизации, другим процессом сборки, более новой версией компилятора.
практический советЕсли вы оказались в ситуации, когда у вас есть несколько статических переменных, которые вам нужно правильно инициализировать, то есть вероятность, что вы захотите выделить их в класс. Этот класс может иметь обычный конструктор и деструктор для инициализации / очистки. Затем вы можете поместить экземпляр этого вспомогательного класса в одну (классовую) статическую переменную. C ++ дает очень строгие гарантии согласованности для вызова ctors и dtors классов, для всего, что доступно официальными средствами (без приведения, без низкоуровневого обмана).
Возможно, вам лучше использовать другой подход. Нужно ли определять статическую информацию внутри StaticInitialized?
Рассмотрите возможность создания отдельного одноэлементного класса с именем SharedData. Первый клиент, который вызывает SharedData :: Instance (), затем инициирует создание коллекции общих данных, которые будут просто обычными данными класса, хотя они будут находиться в одном экземпляре объекта, который размещен статически:
// SharedData.h
class SharedData
{
public:
int m_Status;
bool m_Active;
static SharedData& instance();
private:
SharedData();
}
// SharedData.cpp
SharedData::SharedData()
: m_Status( 0 ), m_Active( true )
{}
// static
SharedData& SharedData::instance()
{
static SharedData s_Instance;
return s_Instance;
}
Любой клиент, заинтересованный в общем сборе данных, теперь должен будет получить к нему доступ через синглтон SharedData, и первый такой клиент, который вызовет SharedData :: instance (), запустит настройку этих данных в ctor SharedData, который будет только когда-либо звонил один раз.
Теперь ваш код предполагает, что разные подклассы могут иметь свои собственные способы инициализации статических данных (через полиморфную природу initializeStatics ()). Но это кажется довольно проблематичной идеей. Действительно ли несколько производных классов предназначены для совместного использования одного набора статических данных, но каждый подкласс будет инициализировать его по-своему? Это будет просто означать, что любой класс, который был создан первым, будет тем, кто устанавливает статические данные по-своему, и тогда любой другой класс должен будет использовать эту настройку. Это действительно то, что вы хотите?
Я также немного озадачен тем, почему вы пытаетесь объединить полиморфизм с частным наследованием. Количество случаев, когда вы действительно хотите использовать частное наследование (в отличие от композиции), очень мало. Мне остается только задаться вопросом, считаете ли вы, что вам нужно, чтобы initializeStatics () был виртуальным, чтобы производный класс мог его вызывать. (Это не так.) Однако вы, похоже, хотите переопределить initializeStatics () в производном классе, по причинам, которые мне не ясны (см. Ранее). Что-то кажется странным во всей настройке.