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

岁酱吖の 提交于 2019-11-26 17:47:59

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

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!

Downloadable version

There's now a GitHub repo for the solution, containing a single header file: 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' .cpp implementation file.

Notes:

  • You must surround your static block code with curly braces.
  • The relative order of execution of static code is not guaranteed in 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 not valid 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.

Live Demo

Angew

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).

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:

// 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 .cpp file is executed.

If you don't want to pollute the class definition, you can also use a 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.


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:

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.

Kerrek SB

You can initialize static data members in 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.

Here is a nice way to mimic a static block using 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.

In C++ there is no such idiom.

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 notion of a "class" anymore, and there is no such thing like code entities loaded on demand by a "classloader".

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_once your 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 *.cpp file).

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-effects of 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).

You may be better off taking a different approach altogether. Does the collection of static information actually need to be defined inside 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.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;
}

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.

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?

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!