2012-03-14 8 views
0

Nifty Counter C++ Idiomで静的メンバーを初期化しようとすると、問題が発生しました。次のような場合にideomを正しく使用する方法を説明できますか?Nifty Counter C++ Idiomを使用するには、コンストラクタを2回コールする必要がありますか?

問題は次のように見えます:私は2つの静的オブジェクトを別のコンパイル単位に持っています。ここでは、他の静的メンバーを使用しています(CDataFile)。この場合の初期化の順序は定義されていないため、プロダクション環境の順序が間違っていたため、Nifty Counterイディオムを使用しようとしました。しかし、今はCDataFileの静的メンバーが2回(コンストラクタが2回呼び出される)初期化されているようです。初めてコンストラクタが呼び出されるのは、CDataFileInitializerです。その後、静的メンバー(​​が塗りつぶされます)が使用されますが、CSomeClassのコンストラクターが2回目に呼び出され、​​の内容がクリアされます。

// datafile.h 
class CDataFile 
{ 
    friend class CDataFileInitializer; 
protected: 
    static CSomeClass mSome; 
    // other code 
}; 

static class CDataFileInitializer 
{ 
public: 
    CDataFileInitializer(); 
} dfinitializer; 

// datafile.cpp 
static int nifty_counter; 
CSomeClass CDataFile::mSome; // second initialization comes from here? 

CDataFileInitializer::CDataFileInitializer() 
{ 
    if (!nifty_counter++) 
    { 
     printf("CDataFileInitializer Constructor\n"); 
     CDataFile::mSome= CSomeClass(); // first initialization 
    } 
} 
+0

これは気の利いたカウンターではありません。これは未定義の動作です。 – Mankarse

+0

@Mankarse:なぜそれが定義されていないのか説明できますか? –

+0

'CSomeClass CDataFile :: mSome'の動的初期化は、' dfinitializer'の動的初期化に関して不確定に配列されています。したがって、 'CDataFile :: mSome'で' operator = 'が呼び出されると、まだ構築されていないかもしれません。 – Mankarse

答えて

3

ライン:

CSomeClass CDataFile::mSome; 

は型CSomeClassの変数を定義します。この変数の初期化は次の2つの段階で行われます。 最初はゼロで初期化されています。これは、おおよそのメモリがすべて0に設定されていることを意味します。 その後、動的初期化が行われます。これにより、コンストラクターが実行されます。

dfinitializerは、同様の「ゼロ初期化後に動的初期化」パターンに従います。動的初期化ステップでは、CDataFile::mSomeのを呼び出して、CSomeClass()を新しいデフォルトに割り当てます。​​に割り当てます。

このステップは、​​とdfinitializerの動的初期化が相互に不確定に順序付けられているため、全く無意味です。 dfinialiserが最初に初期化されると、作成されていないオブジェクト(後にデフォルトで構築されるオブジェクト)に割り当てが試みられ、2番目に初期化されると、すでに作成されたオブジェクトに再割り当てされます。

の代わりに:

CDataFileInitializer::CDataFileInitializer() 
{ 
    if (!nifty_counter++) 
    { 
     printf("CDataFileInitializer Constructor\n"); 
     new (&CDataFile::mSome) CSomeClass(); 
    } 
} 

代替:

alignas(CSomeClass) unsigned char CDataFile::mSome[sizeof(CSomeClass)]; 

はその後にCDataFileInitializerを変更します。

CSomeClass CDataFile::mSome; 

は、オブジェクトを構築することが可能なストレージ領域を作成する必要があります関数静的変数を使用することです:

CSomeClass& getMSome() { 
    static CSomeClass mSome; 
    return mSome; 
} 

​​は、スレッドセーフな方法で遅く初期化されます。

+0

これは最適な解決策です。コンパイラに 'alignas'などのものがあります(また、変数の型についてリンク時の型チェックをしないのですが、そうしないコンパイラは分かりません)。 –

+0

@JamesKanze:私の答えは明らかにしませんでしたが、実際には 'datafile.h 'の' mSome'の型を変更することを提案していました。私はそれがどんなタイプの制約にも違反するのか見ていない。 – Mankarse

+0

オブジェクトを使用するたびに 'reinterpret_cast'が必要な場合、クライアントは非常に幸せにならないでしょう。正式には、あるタイプのオブジェクトを定義し、別のタイプとしてアクセスすることは未定義の動作です。実際には、ヘッダーの 'CSomeClass'として宣言しますが、ソースファイルで行ったように、問題なく動作します。それは私がやっていることです(もし私が 'アライダス'を持つことができるのであれば)。 –

1

名前空間スコープでオブジェクトを定義する場合、そのコンストラクタは、初期化中にいくつかの点で、起動コードによって呼び出さ になります。 がきちんとしたカウンターイディオムを使用したい場合は、何とかこれを抑制する必要があります。 またはノーオペレーションにしてください。また、実際の イニシャライザ内に新しいプレースメントを使用する必要があります。これを達成するには、いくつかの方法があります。

  • 私が見てきた産業強度の実装のほとんどは はコンストラクタが呼び出されませんので を確保するために、アセンブラでオブジェクトを宣言、またはコンパイラの拡張機能を使用しますか。これはあまり移植性がありませんが、iostreamのようなものは となりますが、これは純粋なC++では実装できません。 これはよく受け入れられます。 (彼らが破壊することが許可されていないので、これは、実際には、入出力ストリームオブジェクトのための唯一の受け入れ可能な解決策 です。)

  • 私は一般的に が行う特別なノーオペレーションのコンストラクタを持つように配置されました何もない。正式には動作保証されていませんが、実際には があります。次に、このコンストラクターを使用するために、控え目の カウンターイディオムを使用するインスタンスを定義します。

  • クラスをコントロールしている場合最後に、構築された、とだけ インスタンスが気の利いたカウンタによって制御され、それは些細な コンストラクタを持つことができる場合は、コンストラクタについて作業する必要はありません、ただ が初期化イニシャライザ内のさまざまなメンバこれらの

どれも特に素敵な解決策ではない、新しいコードでは、私は シングルトンイディオムのいくつかのバリアントを使用すると思います。

+0

2番目の点では、何もしない特別なコンストラクタが、すてきなカウンタコード部分で呼び出されるべきであると書かれています。何もせず、特別なコンストラクタを初期化する必要がありますか? 3番目のポイント:イニシャライザでは、イニシャライザリストまたは初期化メソッド 'Init()'を意味しますか?しかし、問題は残っていませんし、静的オブジェクトがnifty counter code-partに作成されていないか、またはすでにゼロ初期化されていますか、後で呼び出されるデフォルトのコンストラクタは(2番目の点のように)何もしませんか? –

+0

@ChristianAmmer 2番目の点は異なりますが、オブジェクトを別の場所で使用する場合は、デフォルトのコンストラクタを正常に動作させたいと考えています。 no-opコンストラクタは特殊なケースであり、特殊な状況でのみ使用されます。そのため、私はそれをマークすることをお勧めします。 niftyカウンタアルゴリズムによって構築されたオブジェクトの定義は、MyClass obj(noopConstructor);のようなものでなければならないので、読者は特別なことが起こっていることを直ちに知ることができます。そして3番目のケースでは、私は自明なコンストラクタを持たないことを提案していました。 –

-1

はいいいえ:はい2回呼び出され、2つの異なるオブジェクトでは呼び出されません。

のヨーヨーが原因A.cppとB.cppは両方dfinitializerのローカルおよび独立したコピーを持っていますの#includeの

// A.cpp 
#include "datafile.h" 
... 

// B.cpp 
#include "datafile.h" 
... 

があるとしましょう。

datafile.cppは、nifty_counter(最初の0の値... static int nifty_counter = 0;)とCDatafile :: mSome(ファイルレベルで初期化した方がよい)を定義しています。

どのようなCDataFileInitializer ctorは、即時に作成され破棄された一時的なCSomeClass()が既に初期化された​​に割り当てられます。

実際、CDataFileが割り当て可能であるという理由だけで、間違った実装が正しいことをしています。

静的データメンバーを初期化するだけであれば、静的メンバー定義(注:定義、宣言ではない)を含むモジュール内の何かが、

CDataFileInitializer[0x406035] creation 
CDataFileInitializer FIRST INITIALIZATION 
CSome[0x40602c] default created 
initializing A.cpp 
CDataFileInitializer[0x406029] creation 
initializing B.cpp 
CDataFileInitializer[0x406025] creation 
main 
do something in a.ccp 
do something in b.ccp 
main return 
CDataFileInitializer[0x406025] destruction 
cleaning B.cpp 
CDataFileInitializer[0x406029] destruction 
cleaning A.cpp 
CSome[0x40602c] destroyed 
CDataFileInitializer[0x406035] destruction 
CDataFileInitializer LAST DESTRUCTION 
:モジュールはのは、次のような出力が得られますより良いトリック

//some.h 
#ifndef SOME_H_INCLUDED 
#define SOME_H_INCLUDED 

#include<iostream> 
class CSome 
{ 
public: 
    CSome() { std::cout << "CSome["<<this<<"] default created" << std::endl; } 
    CSome(const CSome& s) { std::cout << "CSome["<<this<<"] created from ["<<&s<<"]" << std::endl; } 
    CSome& operator=(const CSome& s) { std::cout << "CSome["<<this<<"] assigned from ["<<&s<<"]" << std::endl; return *this; } 
    CSome(CSome&& s) { std::cout << "CSome["<<this<<"] created moving ["<<&s<<"]" << std::endl; } 
    CSome& operator=(CSome&& s) { std::cout << "CSome["<<this<<"] assigned moving ["<<&s<<"]" << std::endl; return *this; } 
    ~CSome() { std::cout << "CSome["<<this<<"] destroyed" << std::endl; } 
}; 
#endif // SOME_H_INCLUDED 




//datafile.h 
#ifndef DATAFILE_H_INCLUDED 
#define DATAFILE_H_INCLUDED 

#include "some.h" 
class CDataFile 
{ 
public: 
protected: 
    static CSome mSome; 
}; 

static class CDataFileInitializer 
{ 
public: 
    CDataFileInitializer(); 
    ~CDataFileInitializer(); 
} datafileinitializer; 


#endif // DATAFILE_H_INCLUDED 



//datafile.cpp 
#include "datafile.h" 
#include <iostream> 
static int nifty_counter = 0; //the one and only 

CSome CDataFile::mSome; //define and initialize 

CDataFileInitializer::CDataFileInitializer() 
{ 
    std::cout << "CDataFileInitializer["<<this<<"] creation"<< std::endl; 
    if(!nifty_counter++) 
    { 
     std::cout << "CDataFileInitializer FIRST INITIALIZATION"<< std::endl; 
    } 
} 

CDataFileInitializer::~CDataFileInitializer() 
{ 
    std::cout << "CDataFileInitializer["<<this<<"] destruction"<< std::endl; 
    if(!--nifty_counter) 
    { 
     std::cout << "CDataFileInitializer LAST DESTRUCTION"<< std::endl; 
    } 
} 


//A.cpp 
#include <iostream> 
static class A 
{ 
public: 
    A() { std::cout << "initializing A.cpp" << std::endl; } 
    ~A() { std::cout << "cleaning A.cpp" << std::endl; } 
} a; 
#include "datafile.h" 
// other a.cpp code ... 

void call_a() { std::cout << "do something in a.ccp" << std::endl; } 


//B.cpp 
#include <iostream> 
static class B 
{ 
public: 
    B() { std::cout << "initializing B.cpp" << std::endl; } 
    ~B() { std::cout << "cleaning B.cpp" << std::endl; } 
} b; 
#include "datafile.h" 
// other b.cpp code ... 

void call_b() { std::cout << "do something in b.ccp" << std::endl; } 


//main.cpp 
#include <iostream> 

void call_a(); 
void call_b(); 

int main() 
{ 
    std::cout << "main" << std::endl; 
    call_a(); 
    call_b(); 
    std::cout << "main return" << std::endl; 
    return 0; 
} 

を試してみましょう...

ので(ちょうど最適化することを避けるため)

コースのうち、アドレスはお使いのマシンによって異なり、実行されます。

関連する問題