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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19227664/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 15:14:53  来源:igfitidea点击:

What's the C++ idiom equivalent to the Java static block?

javac++initializationequivalentstatic-block

提问by einpoklum

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

我有一个包含一些静态成员的类,我想运行一些代码来初始化它们(假设此代码无法转换为简单的表达式)。在 Java 中,我只会做

class MyClass {
    static int myDatum;

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

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

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

I would like solution for both of the following options:

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

  1. Initialization happens when process loads (or when the DLL with this class is loaded).
  2. Initialization happens when the class is first instantiated.
  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 */
    }
};

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

采纳答案by einpoklum

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

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

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!

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

Downloadable version

可下载版本

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

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

Usage

用法

If you write:

如果你写:

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

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' .cppimplementation file.

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

Notes:

笔记:

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

Implementation

执行

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__

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 __attribute((unused)) = (function_name(), 0) ; \
static void function_name()

Notes:

笔记:

  • 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 notvalid C, despite not using any classes or methods.
  • The __attribute ((unused))can 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.
  • 有些编译器不支持__COUNTER__——它不是 C++ 标准的一部分;在这些情况下,上面的代码使用__LINE__,它也有效。GCC 和 Clang 确实支持__COUNTER__.
  • 这是 C++98;您不需要任何 C++11/14/17 构造。然而,它不是有效的 C,尽管没有使用任何类或方法。
  • __attribute ((unused))可被丢弃,或更换[[unused]],如果你有一个C ++编译器11,其不喜欢的GCC风格的未使用的扩展。
  • 这不会避免或帮助静态初始化顺序失败,因为虽然您知道您的静态块将在之前执行main(),但您无法保证相对于其他静态初始化究竟何时发生这种情况。

Live Demo

Live Demo

回答by Kerrek SB

You caninitialize static data members in C++:

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

#include "Bar.h"

Bar make_a_bar();

struct Foo
{
    static Bar bar;
};

Bar Foo::bar = make_a_bar();

You may have to think about inter-translation-unit dependencies, but that's the general approach.

您可能需要考虑翻译单元间的依赖关系,但这是通用方法。

回答by Angew is no longer proud of SO

For #1, if you really need to initialise when the process starts/library is loaded, you'll have to use something platform-specific (such as DllMain on Windows).

对于#1,如果您确实需要在进程启动/加载库时进行初始化,则必须使用特定于平台的东西(例如 Windows 上的 DllMain)。

However, if it's enough for you to run the initialisation before any code from the same .cpp file as the statics is executed, the following should work:

但是,如果在执行来自同一个 .cpp 文件的任何代码之前运行初始化就足够了,那么执行以下操作:

// Header:
class MyClass
{
  static int myDatum;

  static int initDatum();
};

 

 

// .cpp file:
int MyClass::myDatum = MyClass::initDatum();

This way, initDatum()is guaranteed to be called before any code from that .cppfile is executed.

这样,initDatum()保证在.cpp执行该文件中的任何代码之前调用。

If you don't want to pollute the class definition, you can also use a Lambda(C++11):

如果不想污染类定义,也可以使用Lambda(C++11):

// Header:
class MyClass
{
  static int myDatum;
};

 

 

// .cpp file:
int MyClass::myDatum = []() -> int { /*any code here*/ return /*something*/; }();

Don't forget the last pair of parentheses - that actually calls the lambda.

不要忘记最后一对括号——它实际上调用了 lambda。



As for #2, there's one problem: you can't call a virtual function in the constructor. You're better off doing this by hand in the class instead of using a base class for it:

至于#2,有一个问题:你不能在构造函数中调用虚函数。您最好在类中手动执行此操作,而不是使用基类:

class MyClass
{
  static int myDatum;

  MyClass() {
    static bool onlyOnce = []() -> bool {
      MyClass::myDatum = /*whatever*/;
      return true;
    }
  }
};

Assuming the class only has one constructor, that will work just fine; it is thread-safe, as C++11 guarantees such safety for initializing static local variables.

假设该类只有一个构造函数,那将工作得很好;它是线程安全的,因为 C++11 保证了初始化静态局部变量的安全性。

回答by Kaitain

You may be better off taking a different approach altogether. Does the collection of static information actually need to be defined inside StaticInitialized?

您最好采取完全不同的方法。静态信息的集合真的需要在StaticInitialized里面定义吗?

Consider creating a separate singleton class called SharedData. The first client that calls SharedData::Instance() will then trigger the creation of the collection of shared data, which will simply be normal class data, albeit living inside a single object instance that is allocated statically:

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

// SharedData.h

// 共享数据.h

class SharedData
{
public:
    int m_Status;
    bool m_Active;

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

// SharedData.cpp

// 共享数据.cpp

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

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

Any client interested in the shared collection of data would now have to access it via the SharedData singleton, and the first such client to call SharedData::instance() would trigger the setup of that data, in SharedData's ctor, which would only ever be called once.

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

Now your code suggests that different subclasses might have their own ways of initializing static data (through the polymorphic nature of initializeStatics()). But this seems a rather problematic idea. Are multiple derived classes really intended to share a single set of static data, yet each subclass would initialize it differently? This would simply mean that whichever class was constructed first would be the one to set up the static data in its own parochial way, and then every other class would have to use this setup. Is this really what you want?

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

I am also slightly confused as to why you would try to combine polymorphism with private inheritance. The number of cases where you would genuinely want to use private inheritance (as opposed to composition) is very small. I'm left wondering if you somehow believe that you need initializeStatics() to be virtual in order for the derived class to be able to call it. (This is not the case.) Yet you do seem to wish to override initializeStatics() in the derived class, for reasons that are not clear to me (see earlier). Something seems kooky about the whole setup.

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

回答by iammilind

Here is a nice way to mimic a staticblock using C++11:

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

Macro

#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

Usage

用法

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

Demo.

演示

回答by Ichthyo

In C++ there is no such idiom.

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

The reason lies in the entirely different nature of the code generated from C++: The runtime is not "managed". In the generated code, after compilation, there exists no notionof a "class" anymore, and there is no such thing like code entities loaded on demand by a "classloader".

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

There are some elements with roughly comparable behaviour, yet you really need to understand their nature precisely to exploit this behaviour.

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

  • you can build your code into a shared library, which can be loaded dynamically, at runtime.
  • in C++11, you can std::call_onceyour initialisation from a class constructor. However, such code will run late, when the class instance is created, not when the executable or shared library is loaded
  • you can define global variables and (class) static variables with an initialiser. This initialiser can be a function, which allows you to run code when the variable gets initialised. The execution order of these initialisers is well defined only within a single translation unit (e.g. one *.cppfile).
  • 您可以将代码构建到一个共享库中,该库可以在运行时动态加载。
  • 在 C++11 中,您可以从类构造函数进行std::call_once初始化。但是,这样的代码会在创建类实例时运行较晚,而不是在加载可执行文件或共享库时
  • 您可以使用初始化程序定义全局变量和(类)静态变量。这个初始化器可以是一个函数,它允许你在变量被初始化时运行代码。这些初始化程序的执行顺序仅在单个翻译单元(例如一个*.cpp文件)中明确定义。

But you must not assume anything beyond that; esp. you can never be sure if and when this initialisation is actually performed. This warning is for real. Especially do not assume anything about side-effectsof such initialisation code. It is perfectly legal for the compiler to replace such code by something deemed "equivalent" by the compiler. Future compiler versions can be assumed to become more and more clever in that respect. Your code may seem to work, but can break with different optimisation flags, different build process, newer compiler version.

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



practical hint: if you find yourself in the situation that you have several static variables, which you need to initialise properly, then chances are that you want to factor them out into a class. This class can then have a regular constructor and destructor to do the initialisation / clean-up. You may then place an instance of that helper class into a single (class) static variable. C++ gives very strong consistency guarantees for invoking ctors and dtors of classes, for anything which is accessible by official means (no casts, no low-level trickery).

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

回答by noone

In C++17 , u can have the following:-

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

static.hpp :-

静态.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:-

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

This also works in C++11 without [[maybe_unused]]

这也适用于 C++11,无需 [[maybe_unused]]