38

我有一个包含一些静态成员的类,我想运行一些代码来初始化它们(假设这段代码不能转换为简单的表达式)。在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++(目前?)不允许初始化非常量静态成员。但是,至少这将静态块的问题减少为通过表达式进行静态初始化的问题......

4

7 回答 7

18

您也可以在 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,尽管没有使用任何类或方法。
  • 如果您的C++11 编译器不喜欢 GCC 风格的未使用扩展,则__attribute ((unused))可能会被删除或替换。[[unused]]
  • 这不会避免或帮助静态初始化顺序惨败,因为虽然您知道您的静态块将在之前执行main(),但您无法保证相对于其他静态初始化何时发生这种情况。

Live Demo

于 2015-12-16T20:20:02.443 回答
10

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

于 2013-10-07T15:01:41.483 回答
8

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

#include "Bar.h"

Bar make_a_bar();

struct Foo
{
    static Bar bar;
};

Bar Foo::bar = make_a_bar();

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

于 2013-10-07T14:40:05.677 回答
2

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

#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";
  };  
}

演示

于 2016-08-02T08:30:37.233 回答
2

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

静态.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";
}

这也适用于 C++11[[maybe_unused]]

于 2020-03-21T17:28:56.270 回答
1

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

原因在于从 C++ 生成的代码的性质完全不同:运行时不是“托管的”。在生成的代码中,经过编译后,不再存在“类”的概念,也没有“类加载器”按需加载的代码实体之类的东西。

有些元素的行为大致相当,但您确实需要准确了解它们的性质才能利用这种行为。

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

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


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

于 2018-03-18T16:45:51.470 回答
0

完全采用不同的方法可能会更好。静态信息的集合真的需要在StaticInitialized里面定义吗?

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

// 共享数据.h

class SharedData
{
public:
    int m_Status;
    bool m_Active;

    static SharedData& instance();
private:
    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(),原因我不清楚(见前面)。整个设置似乎有些古怪。

于 2015-12-18T16:19:18.183 回答