null

static-blocks в C++

Доброе утро!

В одной из прошлых статей я предлагал вариант воссоздания свойств из 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++ есть статические блоки кода.