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

igetnqfo  于 2023-04-08  发布在  Java
关注(0)|答案(7)|浏览(140)

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

class MyClass {
    static int field1;
    static int field2;

    static {
        /* do some computation which sets field1 and field2 */
    }
}

如果我没弄错的话,C++不允许这样的静态代码块,对吧?我应该怎么做呢?
我想解决以下两个选项:
1.初始化发生在进程加载时(或加载具有此类的DLL时)。
1.初始化发生在类第一次示例化时。
对于第二个选择,我在想:

class StaticInitialized {
    static bool staticsInitialized = false;

    virtual void initializeStatics();

    StaticInitialized() {
        if (!staticsInitialized) {
            initializeStatics();
            staticsInitialized = true;
        }
    }
};

class MyClass : private StaticInitialized {
    static int field1;
    static int field2;

    void initializeStatics() {
        /* computation which sets field1, field2 */
    }
};

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

bkkx9g8r

bkkx9g8r1#

在C++中也可以有静态块-在类之外。

结果我们可以实现一个Java风格的静态块,尽管是在类的外部而不是在类的内部,即在翻译单元范围内。这个实现在幕后有点丑陋,但当使用它时,它是相当优雅的!

可下载版本

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

用法

如果你写:

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

这段代码将在你的main()之前运行。2你可以初始化静态变量或者做任何你喜欢做的事情。3所以你可以在你的类的.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__
  • C98你不需要任何C11/14/17结构。然而,它是 * 非 * 有效的C,尽管没有使用任何类或方法。
  • __attribute ((unused))可能会被删除,或者如果你有一个C++11编译器,它不喜欢GCC风格的未使用的扩展,则用[[unused]]替换。
  • 这并不能避免或帮助静态初始化顺序的失败,因为虽然你知道你的静态块将在main()之前执行,但你不能保证相对于其他静态初始化,什么时候会发生这种情况。

Live Demo

fivyi3re

fivyi3re2#

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

inn6fuwd

inn6fuwd3#

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

#include "Bar.h"

Bar make_a_bar();

struct Foo
{
    static Bar bar;
};

Bar Foo::bar = make_a_bar();

您可能必须考虑翻译单元间的依赖关系,但这是一般的方法。

jdg4fx2g

jdg4fx2g4#

下面是一个使用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

kxe2p93d

kxe2p93d5#

在C++17中,u可以有以下内容:
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__) = []()

主.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中也有效

hs1ihplo

hs1ihplo6#

在C++中没有这样的习惯用法。

原因在于从C++生成的代码的性质完全不同:运行时不是“托管的”。在生成的代码中,编译后,不再存在“类”的概念,也没有“类加载器”按需加载的代码实体。
有一些元素具有大致类似的行为,但您确实需要精确地了解它们的性质才能利用这种行为。

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

但你不能假设任何超出这一点;尤其是你永远无法确定是否以及何时执行了这个初始化。这个警告是真实的的。特别是 * 不要假设任何关于这样的初始化代码的副作用 *。编译器用编译器认为“等效”的东西替换这样的代码是完全法律的的。可以假设未来的编译器版本在这方面会变得越来越聪明。您的代码可能看起来可以工作,但可能会因不同的优化标志,不同的构建过程,较新的编译器版本而中断。

实用提示:如果你发现自己有几个静态变量,你需要正确地初始化,然后你可能想把它们分解成一个类。这个类可以有一个常规的构造函数和析构函数来进行初始化/清理。然后你可以把这个助手类的一个示例放到一个单独的C++为调用类的ctors和dtors提供了非常强的一致性保证,对于任何可以通过官方手段访问的东西(没有强制转换,没有低级技巧)。

rkue9o1l

rkue9o1l7#

你可能最好采取一种完全不同的方法。静态信息的集合实际上需要在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中的该数据的设置,该ctor只会被调用一次。
现在,您的代码表明,不同的子类可能有自己的初始化静态数据的方法(通过initializeStatics()的多态性)。但这似乎是一个相当有问题的想法。多个派生类真的打算共享一组静态数据吗但每个子类的初始化都不一样这意味着无论哪个类首先被构造,它都将以自己的狭隘方式设置静态数据,然后每个其他类都必须使用这个设置。这真的是你想要的吗?
我也有点困惑,为什么你会尝试将多态性和私有继承结合起来(相对于合成)是非常小的。我想知道你是否认为你需要初始化静态()是虚的,以便派生类能够调用它。(实际情况并非如此。)然而,您似乎确实希望覆盖initializeStatics()在派生类中,原因我不清楚(见前面)。整个设置似乎有些古怪。

相关问题