与Java静态块等效的C++习语是什么?

我有一个包含一些静态成员的类,我想运行一些代码来初始化它们(假设此代码无法转换为简单的表达式)。在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++(目前?)不允许初始化非const静态成员。但是,至少这减少了静态块的问题,即通过表达式进行静态初始化的问题......


答案 1

您也可以在C++中使用静态块 - 在类之外。

事实证明,我们可以实现Java风格的静态块,尽管在类之外而不是在类内部,即在翻译单元范围内。该实现在引擎盖下有点丑陋,但是当使用时,它非常优雅!

可下载版本

现在有一个用于该解决方案的GitHub存储库,其中包含一个头文件:static_block.hpp

用法

如果你写:

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__
#ifdef _MSC_VER
#define _UNUSED
#else
#define _UNUSED __attribute((unused))
#endif // _MSC_VER

这是将事情放在一起的宏工作:

#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 _UNUSED = (function_name(), 0) ; \
static void function_name()

笔记:

  • 有些编译器不支持 - 它不是C++标准的一部分;在这些情况下,上面的代码使用 ,这也有效。GCC 和 Clang 确实支持 。__COUNTER____LINE____COUNTER__
  • 这是C++98;你不需要任何C++11/14/17结构。但是,它不是有效的C,尽管不使用任何类或方法。
  • 可能会被删除,或者如果您有一个不喜欢GCC样式未使用的扩展的C++11编译器,则可以将其替换为。__attribute ((unused))[[unused]]
  • 这并不能避免或帮助静态初始化顺序的失败,因为虽然您知道静态块将在之前执行,但相对于其他静态初始化,您不能保证这种情况何时发生。main()

Live Demo


答案 2

对于#1,如果你真的需要在进程启动/加载库时进行初始化,你必须使用特定于平台的东西(例如Windows上的DllMain)。

但是,如果在执行静态文件的同一.cpp文件中的任何代码之前运行初始化就足够了,则以下操作应该有效:

// Header:
class MyClass
{
  static int myDatum;

  static int initDatum();
};

 

// .cpp file:
int MyClass::myDatum = MyClass::initDatum();

这样,可以保证在执行该文件中的任何代码之前调用 。initDatum().cpp

如果您不想污染类定义,还可以使用 Lambda (C++11):

// Header:
class MyClass
{
  static int myDatum;
};

 

// .cpp file:
int MyClass::myDatum = []() -> int { /*any code here*/ return /*something*/; }();

不要忘记最后一对括号 - 它实际上调用lambda。


至于#2,有一个问题:你不能在构造函数中调用虚函数。您最好在类中手动执行此操作,而不是为其使用基类:

class MyClass
{
  static int myDatum;

  MyClass() {
    static bool onlyOnce = []() -> bool {
      MyClass::myDatum = /*whatever*/;
      return true;
    }
  }
};

假设该类只有一个构造函数,这将正常工作;它是线程安全的,因为C++11保证了初始化静态局部变量的安全性。