JP / EN

広告

C++での静的コンストラクタをグローバル変数 + 静的関数で実装

タグ:c++

静的コンストラクタはクラスが初期化されるときに一度だけ呼び出される関数である。 ちょうどインスタンスが初期化されるごとに呼び出されるコンストラクタの クラス全体に対する版だと思えばよい。 C#などではおなじみの機能であり、使いどころもあるのだが残念ながらC++ には言語使用としては実装されていない。
しかしC/C++の「グローバル変数の初期化処理はmain関数の前に実行される」という 仕様を利用すると、クラス初期化用の静的メソッドを先に呼び出す = 静的コンストラクタ に近い動作をさせることができてしまう。これをC++の静的コンストラクタ と呼んでしまうことにしている。

実装例


    #include<iostream>
    class ClassA
    {
    public:
      static int static_constructor()  // 静的コンストラクタ
      {
        std::cout << "ClassA is constructed" << std::endl;
      };
    };

    static int _global_int = ClassA::static_constructor();  // グローバル領域で静的コンストラクタを呼び出す

    int main()
    {
        ClassA a = ClassA();
    }
  
これを実行すると
    $ g++ main.cpp
    $ ./a.out
    ClassA is constructed
    main func
  
のように確かにmain関数の前に静的コンストラクタが呼ばれていることがわかる。 main関数に手を加えずにClassAの初期化処理を挿入できることになるので、 カプセル化の観点からは取り回しが良いというわけだ。

まとめると静的コンストラクタのメリットは
  • クラスごとに持つべきメタデータの設定やクラスの登録処理などをクラス側に持たせられる。
  • どのクラスが本当にコンパイルされたかを静的コンストラクタが呼ばれたかを通して調べることができる。
  • するとコンパイル時に要らなくなったクラスを外部の変更なしに捨てる(コンパイルせずに実行ファイルに含めない)ことができるようになる。
といったところといえるだろう。

静的コンストラクタが活きる例として「文字列を受け取り、もしその文字列と同じ名前のクラスがあればそのインスタンスを生成して返す」 という処理がある。 これはデザインパターンでいうところのファクトリーメソッドを実装するところで使うのだが、 ナイーブにやると文字列を比較する膨大なif文ができてしまう。 しかもこのif文 これをうまいこと静的コンストラクタで「クラス名 -> インスタンスのマップ」を作る処理を実装すると ちょっと楽になる。
      #include<iostream>
      #include<map>
      #include<string>
  
      class Parent
      {
      public:
        static Parent* construct(const std::string& class_name)  // ファクトリーメソッド、クラス名からインスタンスを生成
        {
          return factory[class_name]();
        }
        static std::map<std::string, Parent* (*)()> factory; 
      };
  
      class ClassA: public Parent
      {
      public:
        static Parent* instanciate(){ return new ClassA();}  // インスタンス生成、factoryに登録するためにメソッド化しておく
        static int static_constructor()    // 静的コンストラクタ
        {
          std::cout << "ClassA is registered to Factory" << std::endl;
          Parent::factory["ClassA"] = instanciate;
        };
      };
      
      std::map<std::string, Parent* (*)()> Parent::factory; 
      static int _global_int = ClassA::static_constructor();
      int main()
      {
          std::cout << "main func" << std::endl;
          Parent* inst = Parent::construct("ClassA");
      }
  
この例ではParentを継承したクラス (ClassA) は、各々のインスタンス生成メソッドをParent::factoryに 登録していく、という使い方をする。 こうしてParentは自分の子クラスを把握できるようになり、 さらにはParent::constructで名前ベースでインスタンスを生成できるようになったというわけである。 これなら今後ClassB、ClassCと子クラスがどんどん増えていっても子クラスの管理が破綻しにくくなる。

おすすめ記事

C++ STLで0から1の実数値乱数を生成する

std::vector<bool>はテンプレート特殊化して別物になっている

C++ std::mapを値入りで初期化するのは初期化子リストでOK



このエントリーをはてなブックマークに追加

https://wonderhorn.net/