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

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

I have a class with some static members, and I want to run some code to initialize them (suppose this code cannot be converted into a simple expression). In Java, I would just do

class MyClass {
    static int myDatum;

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

除非我弄错了,C++ 不允许这样的静态代码块,对吧?我应该怎么做?

Unless I'm mistaken, C++ does not allow for such static code blocks, right? What should I be doing instead?

我想要以下两个选项的解决方案:

I would like solution for both of the following options:

  1. 在加载进程时(或加载具有此类的 DLL)时发生初始化.
  2. 在第一次实例化类时进行初始化.

对于第二个选项,我在想:

For the second option, I was thinking of:

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++(目前?)不允许初始化非常量静态成员.但是,至少这将静态块的问题减少到通过表达式进行静态初始化的问题......

but that's not possible, since C++ (at the moment?) does not allow initialization of non-const static members. But, at least that reduces the problem of a static block to that of static initialization by expression...

推荐答案

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

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

You can have static blocks in C++ as well - outside classes.

It turns out we can implement a Java-style static block, albeit outside of a class rather than inside it, i.e. at translation unit scope. The implementation is a bit ugly under the hood, but when used it's quite elegant!

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

There's now a GitHub repo for the solution, containing a single header file: static_block.hpp.

如果你写:

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

此代码将在您的 main() 之前运行.你可以初始化静态变量或做任何你喜欢的事情.所以你可以在你的类的 .cpp 实现文件中放置这样一个块.

this code will run before your main(). And you can initialize static variables or do whatever else you like. So you can place such a block in your class' .cpp implementation file.

注意事项:

  • 您必须用花括号将静态块代码括起来.
  • 静态代码的相对执行顺序是在C++中不保证不保证.
  • You must surround your static block code with curly braces.
  • The relative order of execution of static code is not guaranteed in C++.

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

The static block implementation involves a dummy variable initialized statically with a function. Your static block is actually the body of that function. To ensure we don't collide with some other dummy variable (e.g. from another static block - or anywhere else), we need a bit of macro machinery.

#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

这里是把东西放在一起的宏观工作:

and here is the macro work to put things together:

#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()

注意事项:

  • 一些编译器不支持 __COUNTER__ - 它不是 C++ 标准的一部分;在这些情况下,上面的代码使用 __LINE__,它也可以.GCC 和 Clang 确实支持 __COUNTER__.
  • 这是C++98;您不需要任何 C++11/14/17 构造.但是,它不是有效的 C,尽管没有使用任何类或方法.
  • 如果您的 C++11 编译器不喜欢GCC 风格的未使用扩展.
  • 这不会避免或帮助解决 静态初始化订单惨败,因为虽然您知道您的静态块将在 main() 之前执行,但您无法保证相对于其他静态初始化何时发生这种情况.
  • Some compilers do not support __COUNTER__ - it's not part of the C++ standard; in those cases the code above uses __LINE__, which works too. GCC and Clang do support __COUNTER__.
  • This is C++98; you don't need any C++11/14/17 constructs. However, it's not valid C, despite not using any classes or methods.
  • The __attribute ((unused)) might be dropped, or replaced with [[unused]] if you have a C++11 compiler which doesn't like the GCC-style unused extension.
  • This does not avert or help with the static initialization order fiasco, since while you know your static block will execute before main(), you are not guaranteed when exactly that happens relative to other static initializations.

现场演示

相关文章