Доброе утро!
В одной из прошлых статей я предлагал вариант воссоздания свойств из C# в C++. А в этот раз коллега задался вопросом наличия static-блоков из Java в C++. Попробуем ответить, что они есть.
Предложен следующий подход: пусть есть „дружественная” шаблонная структура, которая в конструкторе вызывает статический метод шаблонного параметра, который проинициализирует статические поля класса. Определим некоторое статик-поле в таком классе, которое проинициализируем этой структурой. Таким образом, конструктор структуры-инициализатора сможет вызвать метод, инициализирующий соответствующий класс. Добавим макросов для красоты, и получится нечто следующее (приводятся заголовочный файл и файл реализации):
#include <iostream>
struct static_initialization_error : std::runtime_error {
static_initialization_error(const std::string &what)
: std::runtime_error{what}
{}
};
template<typename T>
struct __Initializer {
__Initializer() {
static bool called = false;
if (called) {
throw static_initialization_error(
std::string("Double initialization of class ") +
typeid(T).name());
}
called = true;
if (! T::__Init()) {
throw static_initialization_error(std::string("Class ")
+ typeid(T).name()
+ " initialization failed");
}
}
};
#define STATIC_BLOCK(type) \
friend __Initializer<type>; \
static __Initializer<type> __Initializer_; \
static bool __Init()
#define STATIC_INIT(type) \
__Initializer<type> type::__Initializer_{}
class A {
STATIC_BLOCK(A) {
std::cerr << "Static block of A called" << std::endl;
x = 123;
return true;
}
static int x;
public:
void f();
};
struct B {
private:
STATIC_BLOCK(B) {
std::cerr << "Static block of B called" << std::endl;
x = 321;
return true;
}
static int x;
public:
B();
void g();
};
#include "s.hpp"
int A::x;
STATIC_INIT(A);
void A::f() {
std::cerr << "A's x: " << x << std::endl;
}
int B::x;
STATIC_INIT(B);
B::B() {
std::cerr << "B::B()\n";
}
void B::g() {
std::cerr << "B's x: " << x << std::endl;
}
int main(int c, char **v) {
A a1, a2, a3;
B b1, b2, b3;
b2.g(); a1.f();
(void)c;(void)v;
return 0;
}
Вывод этого чуда следующий:
$ ./s
Static block of A called
Static block of B called
B::B()
B::B()
B::B()
B's x: 321
A's x: 123
Код в импровизированных статик-блоках вызван лишь однажды, тогда как объектов было создано несколько.
У данного подхода есть ряд недостатков: необходимость указывать имя класса в параметрах (пока что не придумал, как избавиться); необходимость вызывать инициализацию в файле с реализацией (это проблема подхода вцелом, так как это ограничение C++); и единственный критический — неприменимость для классов, у которых создаются глобальные объекты (если объект будет создан раньше, чем вызван STATIC_INIT
, конструктор будет вызван раньше. Обзовём такое использование undefined behaviour и перестанем считать недостатком).
Для многих классов такой подход вполне применим, так что да, в C++ есть статические блоки кода.