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

原文由 einpoklum 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 655
2 个回答

您也可以在 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()

笔记:

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

现场演示

原文由 einpoklum 发布,翻译遵循 CC BY-SA 4.0 许可协议

对于#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 保证了初始化静态局部变量的安全性。

原文由 Angew is no longer proud of SO 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题