Что такое идиома C ++, эквивалентная статическому блоку Java?

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

class MyClass {
static int myDatum;

static {
/* do some computation which sets myDatum */
}
}

Если я не ошибаюсь, C ++ не допускает такие статические блоки кода, верно? Что я должен делать вместо этого?

Я хотел бы получить решение для обоих следующих вариантов:

  1. Инициализация происходит при загрузке процесса (или при загрузке DLL с этим классом).
  2. Инициализация происходит, когда класс создается впервые.

Для второго варианта я думал о:

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 ++ (на данный момент?) не позволяет инициализировать неконстантные статические члены. Но, по крайней мере, это сводит проблему статического блока к проблеме статической инициализации выражением …

25

Решение

Для # 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 гарантирует такую ​​безопасность для инициализации статических локальных переменных.

9

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

Вы также можете иметь статические блоки в C ++ — вне классов.

Оказывается, мы можем реализовать статический блок в стиле Java, хотя и вне класса, а не внутри него, то есть в области видимости блока перевода. Реализация немного уродливая под капотом, но при использовании она довольно элегантна!

использование

Если вы напишите:

static_block {
std::cout << "Hello static block world!\n";
}

этот код будет выполняться до вашего main(), И вы можете инициализировать статические переменные или делать все что угодно. Таким образом, вы можете разместить такой блок в своем классе .cpp файл реализации.

Заметки:

  • Вы должен окружить статический блочный код фигурными скобками.
  • Относительный порядок выполнения статического кода не гарантируется в C ++.

Реализация

В реализации статического блока используется фиктивная переменная, которая статически инициализируется с помощью функции. Ваш статический блок на самом деле является телом этой функции. Чтобы гарантировать, что мы не столкнемся с какой-либо другой фиктивной переменной (например, из другого статического блока — или где-либо еще), нам нужно немного макро-машин.

#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__,
  • Это C ++ 98; вам не нужны никакие C ++ 11/14/17 конструкции. Тем не менее, это не допустимый C, несмотря на то, что не использует никаких классов или методов.
  • __attribute ((unused)) может быть сброшен или заменен [[unused]] если у вас есть компилятор C ++ 11, которому не нравится неиспользуемое расширение в стиле GCC.
  • Это не предотвращает и не помогает с статический порядок инициализации фиаско, с тех пор как вы знаете, ваш статический блок будет выполняться раньше main()Вы не гарантированы, когда именно это происходит относительно других статических инициализаций.

Live Demo

9

Вы Можно инициализировать статические члены данных в C ++:

#include "Bar.h"
Bar make_a_bar();

struct Foo
{
static Bar bar;
};

Bar Foo::bar = make_a_bar();

Возможно, вам придется подумать о зависимостях между переводами, но это общий подход.

6

Вот хороший способ подражать 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";
};
}

демонстрация.

1

В C ++ такой идиомы нет.

Причина кроется в совершенно другой природе кода, сгенерированного из C ++: среда выполнения не «управляемая». В сгенерированном коде после компиляции существует нет понятия больше «класса», и нет такой вещи как сущности кода, загружаемые по требованию «загрузчиком классов».

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

  • Вы можете встроить свой код в общую библиотеку, которая может быть загружена динамически во время выполнения.
  • в C ++ 11 вы можете станд :: call_once ваша инициализация из конструктора класса. Однако такой код будет выполняться поздно, когда создается экземпляр класса, а не когда загружен исполняемый файл или разделяемая библиотека
  • Вы можете определить глобальные переменные и (класс) статические переменные с помощью инициализатора. Этот инициализатор может быть функцией, которая позволяет запускать код при инициализации переменной. Порядок выполнения этих инициализаторов четко определен только в пределах одного блока перевода (например, одного *.cpp файл).

Но вы не должны предполагать ничего сверх этого; особенно Вы никогда не можете быть уверены, если и когда эта инициализация действительно выполняется. Это предупреждение по-настоящему. Особенно не принимайте ничего о побочных эффектах такого кода инициализации. Для компилятора совершенно законно заменить такой код чем-то, что компилятор считает «эквивалентным». Можно предположить, что будущие версии компиляторов станут более умными в этом отношении. Может показаться, что ваш код работает, но может сломаться с разными флагами оптимизации, другим процессом сборки, более новой версией компилятора.


практический советЕсли вы оказались в ситуации, когда у вас есть несколько статических переменных, которые вам нужно правильно инициализировать, то есть вероятность, что вы захотите выделить их в класс. Этот класс может иметь обычный конструктор и деструктор для инициализации / очистки. Затем вы можете поместить экземпляр этого вспомогательного класса в одну (классовую) статическую переменную. C ++ дает очень строгие гарантии согласованности для вызова ctors и dtors классов, для всего, что доступно официальными средствами (без приведения, без низкоуровневого обмана).

1

Возможно, вам лучше использовать другой подход. Нужно ли определять статическую информацию внутри 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 () в производном классе, по причинам, которые мне не ясны (см. Ранее). Что-то кажется странным во всей настройке.

0
По вопросам рекламы [email protected]