Singleton破棄


Singletonのデストラクタはいつ実行されるのでしょうか?

それは、構築方法によって異なります。
(1) クラススコープのstatic変数とする場合
(2) 関数スコープのstatic変数とする場合
(3) new してヒープにつくる場合

(1)と(2)の場合は、プロセス終了時(ダイナミックリンクライブラリならライブラリアンロード時)にデストラクタが呼ばれます。では、他のstatic変数のデストラクタとの関係はどうでしょうか?

static変数のデストラクタはstatic変数のコンストラクタの完了と逆順で実行されます。最初に構築されたstatic変数が最後に破棄されるわけです。この順序をプログラマーが変えることはできません。しかし、場合によってはそれではまずいでしょう…

たとえばLoggingクラスを(2)の方法で実装していて、SomeObjectとOtherObjectというクラスのstatic変数のコンストラクタとデストラクタでLoggingクラスを使用していたとします(極端な例かもしれませんが)…

static class SomeObject {
public:
 ...
 SomeObject() {
   // OtherObjectのコンストラクタより先に実行される
   // ここではLoggingクラスを使用しない…
 }
 ~SomeObject() {
   // OtherObjectのデストラクタより後に実行される
   Logging::Instance().trace(...); // Loggingはすでに破棄されている…
 }
 ...
} someObject;

static class OtherObject {
public:
 ...
 OtherObject() {
   // SomeObjectのコンストラクタの後に実行される
   Logging::Instance().trace(...); // このタイミングではじめてLoggingを構築
 }
 ~OtherObject() {
   // SomeObjectのデストラクタの前に実行される
 }
 ...
} otherObject;

それぞれのオブジェクトのコンストラクタの完了とデストラクタの実行順を並べてみると…

SomeObjectのコンストラクタが完了
⇒ Loggingのコンストラクタが完了
⇒ OtherObjectのコンストラクタが完了
⇒ OtherObjectのデストラクタを実行
⇒ Loggingのデストラクタを実行
⇒ SomeObjectのデストラクタを実行

となり、SomeObjectのデストラクタの中ですでに破棄されたLogging(死んだ参照)にアクセスしてしまうことになります。

気をつけましょう♪

次に(3)の場合のデストラクタは前の記事の実装のままだと呼ばれません(笑)

まあ、プロセス終了時にプロセスが確保したリソースは解放されるので問題なかったりするのですが、でもそれだと気持ち悪いという方はSingletonを破棄するための関数を用意するといいでしょう。

class Logging {
public:
 static Logging& Instance();
 ...
private:
 ...
 static void Destruct();
 ...
};

Logging* Logging::singleton = 0;

Logging& Logging::Instance() {
 if (0 == singleton)
 {
   singleton = new Logging;
   atexit(&Destruct);
 }
 return *singleton;
}

void Logging::Destruct() {
 delete singleton;
 singleton = 0;
}

Destruct関数をpublicにして任意のタイミングで呼ばせるというのもありでしょう。上の例ではC言語の標準関数であるatexit関数を使って、プロセス終了時にDestruct関数が呼ばれるように登録しています。気付かれた方もいるかもしれませんが、上の実装だと先ほどの SomeObject/OtherObjectの例でみた死んだ参照にアクセスした場合、singletonが再び構築されます。『Modern C++ Design』ではこれをPhoenix Singletonと呼んでいます。もし、いちど破棄されたsingletonを再び構築したくなかったら次のようにして例外を投げるなどすればいいでしょう…

...
bool Logging::was_destroyed = false;

Logging& Logging::Instance() {
 if (0 == singleton)
 {
   if (was_destroyed)
   {
      throw std::runtime_error("Logging instance was already destroyed!");
   }
   else
   {
      singleton = new Logging;
      atexit(&Destruct);
   }
 }
 return *singleton;
}

void Logging::Destruct() {
 delete singleton;
 singleton = 0;
 was_destroyed = true;
}

タグ:

+ タグ編集
  • タグ:

このサイトはreCAPTCHAによって保護されており、Googleの プライバシーポリシー利用規約 が適用されます。

最終更新:2009年06月07日 15:57