分類:[C/C++]
2015/01/22(Thu) 18:17:49 編集(投稿者)
開発環境: Visual C++ 2010 / Windows 7
お世話になります。
ヘッダファイルで提供するクラステンプレートがありまして、このテンプレート内部からのみ使う
シングルトンクラスがあります。
(クラステンプレートの動作を高速化するためのプールです)
そして、このシングルトンクラスの生成をスレッドセーフにしたいのです。
提供するクラステンプレートがヘッダファイルのみで提供する形式なのでヘッダファイルのみで
記述したいと思っています。
そこで、提供するクラステンプレートと同じヘッダファイルに下記のように記述しました。
#頑張って考えたんですけど、自己流なので致命的な穴があるかもしれません。
#逆に既知のアイデアであって、それの下手な実装という可能性もあります。
class MyPool
{
MyPool()
{
// いろいろ処理...
}
MyPool(const MyPool& rhs); // コピー禁止
const MyPool& operator =(const MyPool& rhs); // 代入禁止
public:
static MyPool& GetInstance()
{
static MyPool* volatile pInstance;
// 高速化のためにpInstanceが0であることを最初に確認
if (!pInstance)
{
// 実体を動的に生成(スレッド競合があれば複数生成されるかもしれない)
MyPool* pTmp = new MyPool();
// そこでCASでアトミックにpInstanceに設定(設定できるのは最初の1スレッドだけ)
if (InterlockedCompareExchangePointer(reinterpret_cast<volatile PVOID*>(&pInstance), pTmp, 0) != 0)
{
// 失敗したら既に先行スレッドがpInstanceに実体を設定した後ということ。
// この実体は不要なので破棄します。
delete pTmp;
}
}
return *(static_cast<MyPool* const volatile &>(pInstance));
}
// 各種実装...
};
※このコードはMyPoolのコンストラクターが例外を送出しないことを前提としています。
とりあえず正常に動作しているように見えますが、何点か疑問点と問題があります。
1.GetInstance()内のpInstanceは初回に呼ばれたときに必ず 0 であることを期待しています。
JIS X3014:2003 6.7の4項によると、『静的記憶期間を持つ全ての局所オブジェクトは、そのゼロ初期化を
その他の全ての初期化が起きる前に実行する』とあるので、0 であるか比較するのは問題ないと考えています。
また、ゼロ初期化がスレッド競合により複数回発生することもないと考えています。
この解釈で大丈夫でしょうか?
2.実体を動的に確保しているので、デバッグ時に Visual Studio にメモリーリークがあると怒られてしまいます。
出来れば動的な確保はしたくはないのですが、動的な確保なしにスレッドセーフにする方法を思いつけませんでした。
プロセス終了時に解放されるはずなので問題はないと思いますが、解消できる方法があれば解消したいです。
お知恵をお貸しいただけると嬉しいです。
3. volatile 修飾をつけるべきかどうか迷っています。
最適化による弊害を防ぐためにポインター変数自体にvolatile 修飾を付けています。
また、return 時にもポインター変数を一旦 const volatile な参照にキャストしています。
Visual C++ 2010 では何もつけなくてもリリースビルドで正常に動作しますし、
実際には不要ではないのかとも思っています。
でも根拠がないので念のために修飾しています。
ご意見を聞かせていただけると嬉しいです。
または、『こんな方法じゃ根本的ダメだ』等ありましたらヒントやキーワードだけでも結構ですので、
ご教示いただけると嬉しいです。
1/22 18:17 誤記の修正と例外についての制限事項を追記しました。