web-dev-qa-db-ja.com

Java静的ブロックに相当するC ++イディオムは何ですか?

いくつかの静的メンバーを持つクラスがあり、それらを初期化するためにいくつかのコードを実行したい(このコードを単純な式に変換できないと想定)。 Javaでは、私はただやります

class MyClass {
    static int myDatum;

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

誤解しない限り、C++ではそのような静的コードブロックを許可していません。代わりに何をすべきですか?

次の両方のオプションの解決策が欲しい:

  1. 初期化は、プロセスが読み込まれたとき(またはDLLが読み込まれたとき)に行われます。
  2. 初期化は、クラスが最初にインスタンス化されるときに行われます。

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 ++(現時点では?)では非const静的メンバーの初期化が許可されていないため、これは不可能です。しかし、少なくともそれにより、静的ブロックの問題が式による静的初期化の問題に減少します...

30
einpoklum

C++の静的ブロックは、クラスの外部にも持つことができます。

実は、Javaスタイルの静的ブロックを実装することができます。ただし、クラスの内部ではなく、クラスの外部、つまり、翻訳単位のスコープで実装できます。実装は内部的に少し醜いですが、使用すると非常にエレガントです!

ダウンロード可能なバージョン

ソリューションの GitHub repo があり、単一のヘッダーファイル _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__
_

そして、これは物事をまとめるためのマクロ作業です:

_#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の構成は必要ありません。ただし、クラスまたはメソッドを使用していないにもかかわらず、not有効なCです。
  • GCCスタイルの未使用の拡張機能を好まないC++ 11コンパイラーがある場合、__attribute ((unused))を削除するか、_[[unused]]_で置き換えることができます。
  • これは 静的初期化順序fiasco を回避または支援しません。静的ブロックがmain()の前に実行されることはわかっていますが、それが他の静的ブロックに対して正確に発生するかどうかは保証されません初期化。

Live Demo

12
einpoklum

#1の場合、プロセスの開始時やライブラリのロード時に本当に初期化する必要がある場合は、プラットフォーム固有の何か(WindowsのDllMainなど)を使用する必要があります。

ただし、staticsと同じ.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*/; }();
_

括弧の最後のペアを忘れないでください-実際にはラムダを呼び出します。


#2に関しては、1つの問題があります。コンストラクターで仮想関数を呼び出せないことです。基本クラスを使用する代わりに、クラスで手動でこれを行う方がよいでしょう。

_class MyClass
{
  static int myDatum;

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

クラスにコンストラクタが1つしかないと仮定すると、それは問題なく機能します。 C++ 11は静的ローカル変数を初期化するためのそのような安全性を保証するため、スレッドセーフです。

10
Angew

can C++で静的データメンバーを初期化します。

#include "Bar.h"

Bar make_a_bar();

struct Foo
{
    static Bar bar;
};

Bar Foo::bar = make_a_bar();

翻訳単位間の依存関係について考える必要があるかもしれませんが、それが一般的なアプローチです。

6
Kerrek SB

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

デモ

1
iammilind

C++では、そのようなイディオムはありません。

その理由は、C++から生成されたコードのまったく異なる性質にあります。ランタイムは「管理」されていません。生成されたコードでは、コンパイル後、「クラス」の概念はもはや存在せず、「クラスローダー」によってオンデマンドでロードされるコードエンティティのようなものはありません。

ほぼ同等の動作を持ついくつかの要素がありますが、この動作を活用するには、その性質を正確に理解する必要があります。

  • 実行時に動的にロードできる共有ライブラリにコードをビルドできます。
  • c ++ 11では、次のことができます std :: call_once クラスコンストラクターからの初期化。ただし、このようなコードは、実行可能ファイルまたは共有ライブラリが読み込まれたときではなく、クラスインスタンスが作成されたときに遅く実行されます。
  • イニシャライザを使用して、グローバル変数と(クラス)静的変数を定義できます。この初期化子は関数にすることができます。これにより、変数が初期化されたときにコードを実行できます。これらのイニシャライザの実行順序は、単一の翻訳単位(たとえば、1つの*.cppファイル)。

しかし、それ以上のものを想定してはいけません。特にこの初期化が実際に行われるかどうか、いつ行われるかは決してわかりません。 この警告は本当です。特には、このような初期化コードの副作用については何も想定していません。コンパイラがそのようなコードをコンパイラが「同等」と見なすものに置き換えることは完全に合法です。将来のコンパイラバージョンは、その点でますます賢くなると考えられます。あなたのコードは機能しているように見えるかもしれませんが、異なる最適化フラグ、異なるビルドプロセス、新しいコンパイラバージョンで壊れる可能性があります。


実用的なヒント:適切に初期化する必要がある静的変数がいくつかある状況にある場合、因数分解したい可能性がありますそれらをクラスに出します。このクラスには、初期化/クリーンアップを行うための通常のコンストラクターとデストラクターがあります。次に、そのヘルパークラスのインスタンスを単一の(クラス)静的変数に配置できます。 C++は、公式な手段でアクセスできるもの(キャストなし、低レベルのトリックなし)について、クラスのctorおよびdtorの呼び出しに対して非常に強力な一貫性を保証します。

0
Ichthyo

完全に別のアプローチを取る方が良いでしょう。静的情報のコレクションは、実際には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()をオーバーライドしたいようです。全体のセットアップについて何かがおかしいようです。

0
Kaitain