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

时间:2013-10-07 14:38:11

标签: java c++ initialization equivalent static-block

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

class MyClass {
    static int myDatum;

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

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

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

  1. 当进程加载时(或加载了此类的DLL时)发生初始化。
  2. 首次实例化类时会发生初始化。
  3. 对于第二种选择,我在考虑:

    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静态成员。但是,至少可以通过表达式将静态块的问题减少到静态初始化的问题......

7 个答案:

答案 0 :(得分:10)

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

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

用法

如果你写:

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

此代码将在main()之前运行。您可以初始化静态变量或执行您喜欢的任何其他操作。所以你可以在课堂上放置这样一个块。 .cpp实施文件。

注意:

  • 必须用大括号包围您的静态块代码。
  • 执行静态代码的相对顺序为not guaranteed in 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__

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

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

备注:

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

Live Demo

答案 1 :(得分:9)

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

答案 2 :(得分:6)

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

#include "Bar.h"

Bar make_a_bar();

struct Foo
{
    static Bar bar;
};

Bar Foo::bar = make_a_bar();

您可能需要考虑翻译间单位依赖关系,但这是一般方法。

答案 3 :(得分:2)

在C ++ 17中,您可以具有以下内容:-

static.hpp:-

#define M_CON(A, B) M_CON_(A, B)
#define M_CON_(A, B) A##B

#define STATIC_BLOCK \
        [[maybe_unused]] static const auto M_CON(_static_block,__LINE__) = []()

main.cpp:-

#include <iostream>
#include "static.hpp"

STATIC_BLOCK {
   std::cout << "my static block" << '\n';
   int A,B,C,D = 12;
   std::cout << "my static block with " << A << '\n';    
   return 0; // this return is must
}();

int main(){
    std::cout << "in main function\n";
}

这在没有[[maybe_unused]]的C ++ 11中也适用

答案 4 :(得分:1)

这是使用C ++ 11模拟static块的一种很好的方法:

#define CONCATE_(X,Y) X##Y
#define CONCATE(X,Y) CONCATE_(X,Y)
#define UNIQUE(NAME) CONCATE(NAME, __LINE__)

struct Static_ 
{
  template<typename T> Static_ (T only_once) { only_once(); }
  ~Static_ () {}  // to counter "warning: unused variable"
};
// `UNIQUE` macro required if we expect multiple `static` blocks in function
#define STATIC static Static_ UNIQUE(block) = [&]() -> void

用法

void foo ()
{
  std::cout << "foo()\n";
  STATIC
  {
    std::cout << "Executes only once\n";
  };  
}

Demo

答案 5 :(得分:1)

在C ++中没有这样的习语。

原因在于从C ++生成的代码完全不同:运行时不是“托管”的。在生成的代码中,在编译之后,再存在“类”的 no notion ,并且没有像“类加载器”那样按需加载代码实体的东西。

有些元素的行为大致相当,但你真的需要准确理解它们的本质才能利用这种行为。

  • 您可以将代码构建到共享库中,该库可以在运行时动态加载。
  • 在C ++ 11中,您可以从类构造函数std::call_once初始化。但是,这样的代码将在创建类实例时运行较晚,而不是在加载可执行文件或共享库时
  • 您可以使用初始化程序定义全局变量和(类)静态变量。此初始化程序可以是一个函数,它允许您在变量初始化时运行代码。这些初始化程序的执行顺序仅在单个翻译单元(例如,一个*.cpp文件)内很好地定义。

但除此之外,你不能假设任何事情; ESP。您永远无法确定是否以及何时实际执行此初始化。 此警告适用于。特别是不假设这种初始化代码的副作用。编译器将这些代码替换为编译器认为“等效”的代码是完全合法的。在这方面,可以假设未来的编译器版本变得越来越聪明。您的代码似乎可以工作,但可以打破不同的优化标志,不同的构建过程,更新的编译器版本。

实用提示:如果您发现自己有几个静态变量,需要正确初始化,那么您可能希望将它们分解为一个类。然后,这个类可以有一个常规的构造函数和析构函数来进行初始化/清理。然后,您可以将该帮助程序类的实例放入单个(类)静态变量中。对于可以通过官方方式访问的任何东西(没有强制转换,没有低级欺骗),C ++为调用类的ctors和dtors提供了非常强的一致性保证。

答案 6 :(得分:0)

你可能最好采取不同的方法。实际上是否需要在StaticInitialized中定义静态信息的集合?

考虑创建一个名为SharedData的单独的单例类。然后,调用SharedData :: Instance()的第一个客户端将触发创建共享数据集合,这将只是普通的类数据,尽管它位于静态分配的单个对象实例中:

// SharedData.h

class SharedData
{
public:
    int m_Status;
    bool m_Active;

    static SharedData& instance();
private:
    SharedData();
}

// SharedData.cpp

SharedData::SharedData()
: m_Status( 0 ), m_Active( true )
{}

// static 
SharedData& SharedData::instance()
{
    static SharedData s_Instance;
    return s_Instance;
}

任何对共享数据集感兴趣的客户现在都必须通过SharedData单例访问它,并且第一个调用SharedData :: instance()的客户端将触发在SharedData的ctor中设置该数据,只被召唤一次。

现在你的代码表明不同的子类可能有自己的初始化静态数据的方式(通过initializeStatics()的多态性)。但这似乎是一个相当棘手的想法。多个派生类是否真的要共享一组静态数据,但是每个子类都会以不同的方式初始化它?这只是意味着首先构建的类是以自己的狭隘方式设置静态数据的类,然后每个其他类都必须使用此设置。这真的是你想要的吗?

对于为什么要尝试将多态性与私有继承相结合,我也有点困惑。你真正想要使用私有继承(而不是组合)的情况非常少。我想知道你是否认为你需要initializeStatics()是虚拟的,以便派生类能够调用它。 (事实并非如此。)但是你似乎希望覆盖派生类中的initializeStatics(),原因我不清楚(见前文)。整个设置似乎有点怪异。