什么是与Java静态块等效的C++习惯用法?

ein*_*ica 28 c++ java initialization static-block equivalent

我有一个带有一些静态成员的类,我想运行一些代码来初始化它们(假设这段代码不能转换成简单的表达式).在Java中,我会这样做

class MyClass {
    static int myDatum;

    static {
        /* do some computation which sets myDatum */
    }
}
Run Code Online (Sandbox Code Playgroud)

除非我弄错了,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 */
    }
};
Run Code Online (Sandbox Code Playgroud)

但这是不可能的,因为C++(目前?)不允许初始化非const静态成员.但是,至少可以通过表达式将静态块的问题减少到静态初始化的问题......

ein*_*ica 12

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

事实证明,我们可以实现一个Java风格的静态块,虽然在类之外而不是在它内部,即在翻译单元范围.实施起来有点丑陋,但使用时它非常优雅!

用法

如果你写:

static_block {
    std::cout << "Hello static block world!\n";
}
Run Code Online (Sandbox Code Playgroud)

此代码将在您的代码之前运行static_block.hpp.您可以初始化静态变量或执行您喜欢的任何其他操作.因此,您可以在类的main()实现文件中放置这样的块.

笔记:

履行

静态块实现涉及使用函数静态初始化的虚拟变量.您的静态块实际上是该函数的主体.为了确保我们不与其他虚拟变量(例如来自另一个静态块 - 或其他任何地方)发生冲突,我们需要一些宏机制.

#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__
Run Code Online (Sandbox Code Playgroud)

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

#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()
Run Code Online (Sandbox Code Playgroud)

笔记:

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

Live Demo


Rei*_*ica 10

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

但是,如果它足以让您在执行静态的同一.cpp文件中的任何代码之前运行初始化,则以下内容应该有效:

// Header:
class MyClass
{
  static int myDatum;

  static int initDatum();
};
Run Code Online (Sandbox Code Playgroud)

 

// .cpp file:
int MyClass::myDatum = MyClass::initDatum();
Run Code Online (Sandbox Code Playgroud)

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

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

// Header:
class MyClass
{
  static int myDatum;
};
Run Code Online (Sandbox Code Playgroud)

 

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

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


至于#2,有一个问题:你不能在构造函数中调用虚函数.你最好在课堂上手工完成,而不是使用它的基类:

class MyClass
{
  static int myDatum;

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

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


Ker*_* SB 6

可以在C++中初始化静态数据成员:

#include "Bar.h"

Bar make_a_bar();

struct Foo
{
    static Bar bar;
};

Bar Foo::bar = make_a_bar();
Run Code Online (Sandbox Code Playgroud)

您可能不得不考虑跨翻译单元依赖关系,但这是一般方法.