1

3 つの異なるライブラリがあり、それぞれが異なる開発者によって開発されており、それぞれが (おそらく) 適切に設計されています。しかし、一部のライブラリは RAII を使用しており、一部は使用していないため、一部のライブラリは動的にロードされ、他のライブラリはそうでないため、機能しません。

それぞれの開発者は、自分のしていることは正しいと言っています。この場合のためだけに方法論を変更すれば (たとえば、B で RAII シングルトンを作成するなど)、問題は解決しますが、醜いパッチのように見えます。

この問題をどのように解決することをお勧めしますか?

問題を理解するには、コードを参照してください。


私のコード:

static A* Singleton::GetA()
{
    static A* pA = NULL;
    if (pA == NULL)
    {
        pA = CreateA();
    }
    return pA;
}

Singleton::~Singleton()  // <-- static object's destructor, 
                         // executed at the unloading of My Dll.
{
     if (pA != NULL)
     {
         DestroyA();
         pA = NULL;
     }
}

「A」コード(私のDllに静的にリンクされた別のDll内):

A* CreateA()
{
    // Load B Dll library dynamically
    // do all other initializations and return A*
}
void DestroyA()
{
    DestroyB();
}

「B」コード (A から動的にロードされる別の Dll 内):

static SomeIfc* pSomeIfc;
void DestroyB()
{
    if (pSomeIfc != NULL)
    {
        delete pSomeIfc;  // <-- crashes because the Dll B was unloaded already,
                          // since it was loaded dynamically, so it is unloaded
                          // before the static Dlls are unloaded.
        pSomeIfc = NULL;
    }
}
4

5 に答える 5

11

私の答えは、人々がシングルトンについて続けるたびと同じです。

シングルトンを使用すると、一部のライブラリがアンロードされた後、デストラクタの呼び出しが遅すぎます。「静的」を削除して通常のオブジェクトにし、より狭いスコープでインスタンス化すると、ライブラリがアンロードされる前に破棄され、すべてが再び機能するはずです。

シングルトンは使用しないでください。

于 2009-12-30T12:36:57.560 に答える
5

まず、与えられた例では、で静的ローカル変数として宣言したため、どのようSingleton::~Singleton()にアクセスできるかわかりません。pApASingleton::getA()

次に、「B」ライブラリが動的に読み込まれると説明しましたA* Create();。では、どこでアンロードされますか?void DestroyA()を呼び出す前に「B」ライブラリのアンロードが行われないのはなぜDestroyB()ですか?

そして、「DLL を静的にリンクする」ことを何と呼びますか?

最後に、私は厳しいことを言いたくはありませんが、シングルトンをあらゆる場所に配置するより良い設計が確かに存在します。つまり、「A」の存続期間を管理するオブジェクトがシングルトンであることが本当に必要ですか? CreateA()アプリケーションの開始時に呼び出して、呼び出しをatexit行うことができDestroyA()ます。

編集:「B」ライブラリをアンロードするために OS に依存しているので、実装か​​らDestroyB()呼び出しを削除しないでください。DestroyA()次に、DestroyB()「B」ライブラリがRAIIを使用してアンロードするときに呼び出しを行います(または、Windowsから呼び出してLinuxまたはMacでDllMainマークするなどのOS固有のメカニズムでも)。__attribute__((destructor))

EDIT2:どうやら、あなたのコメントから、あなたのプログラムには多くのシングルトンがあります。それらが静的なシングルトン (マイヤーズ シングルトンとして知られている) であり、相互に依存している場合、遅かれ早かれ壊れます。破壊の順序を制御するには (したがって、シングルトンの有効期間)、いくつかの作業が必要です。Alexandrescu の本また​​はコメントで @Martin が提供するリンクを参照してください。


参考文献 (少し関連があり、読む価値があります)

クリーン コード トーク - グローバルな状態とシングルトン

一度では十分ではありません

高性能シングルトン

最新の C++ 設計、シングルトンの実装

于 2009-12-30T08:35:41.917 に答える
3

最初はこれは API の決闘の問題のように見えますが、実際には別の静的デストラクタの問題です。

一般に、発見した理由だけでなく、他の理由から、グローバルまたは静的デストラクタから自明でないことを行うことを避けるのが最善です。

特に: Windows では、DLL 内のグローバルおよび静的オブジェクトのデストラクタは特別な状況で呼び出され、その動作には制限があります。

DLL が C ランタイム ライブラリ (CRT) にリンクされている場合、CRT によって提供されるエントリ ポイントは、グローバルおよび静的 C++ オブジェクトのコンストラクターとデストラクターを呼び出します。したがって、DllMain のこれらの制限は、コンストラクターとデストラクター、およびそれらから呼び出されるすべてのコードにも適用されます。

http://msdn.microsoft.com/en-us/library/ms682583%28VS.85%29.aspx

制限はそのページで説明されていますが、あまりよくありません。おそらく、シングルトンを使用する代わりに、A の API (明示的な作成および破棄関数を使用) を模倣することによって、問題を回避しようとします。

于 2009-12-30T16:31:27.600 に答える
2

MS C ランタイムでの atexit の動作の副作用が見られます。

これは順序です:

  • 実行可能ファイルおよび実行可能ファイルにリンクされた静的ライブラリで定義されたハンドラーの atexit ハンドラーを呼び出す
  • すべてのハンドルを解放する
  • すべての dll で定義されているハンドラーの atexit ハンドラーを呼び出す

したがって、ハンドルを介して B の dll を手動でロードすると、このハンドルが解放され、自動的にロードされる dll にある A のデストラクタで B が使用できなくなります。

過去に、シングルトンのクリティカルセクション、ソケット、およびデータベース接続をクリーンアップする際に同様の問題が発生しました。

解決策は、シングルトンを使用しないか、必要に応じてメイン出口の前にクリーンアップすることです。例えば

int main(int argc, char * argv [])
{
    A* a = Singleton::GetA();

    // Do stuff;

   Singleton::cleanup();
   return 0;
}

または、後片付けに RIIA を使用することをお勧めします。

int main(int argc, char * argv [])
{
    Singleton::Autoclean singletoncleaner;  // cleans up singleton when it goes out of scope.

    A* a = Singleton::GetA();

    // Do stuff;

   Singleton::cleanup();
   return 0;
}

シングルトンの問題の結果として、私はシングルトンを完全に避けようとしています。どこでも同じオブジェクトにアクセスする必要があり、参照を渡したくない場合は、それをメインのインスタンスとして構築し、コンストラクターはそれ自体をインスタンスとして登録し、他の場所ではシングルトンのようにアクセスします。それがシングルトンではないことを知る必要がある唯一の場所はメインです。

Class FauxSingleton
{
public:
    FauxSingleton() {
        // Some construction
        if (theInstance == 0) {
            theInstance = this;
        } else {
            // could throw an exception here if it makes sense.
            // I generaly don't as I might use two instances in a unit test
        }
    }

    ~FauxSingleton() {
        if (theInstance == this) {
            theInstance = 0;
        }
    }

    static FauxSingleton * instance() {
        return theInstance;
    }

    static FauxSingleton * theInstance;
};


int main(int argc, char * argv [])
{
    FauxSingleton fauxSingleton;

    // Do stuff;
}

// Somewhere else in the application;
void foo() 
{
   FauxSingleton faux = FauxSingleton::instance();
   // Do stuff with faux;
}

明らかにコンストラクタとデストラクタはスレッド セーフではありませんが、通常はスレッドが生成される前に main で呼び出されます。これは、アプリケーションの存続期間中 Orb が必要であり、関係のない多くの場所で Orb にアクセスする必要がある CORBA アプリケーションで非常に便利です。

于 2009-12-30T16:59:19.867 に答える
1

簡単な答えは、DLL を動的にロード/アンロードする独自のコードを作成しないことです。
すべての詳細を処理するフレームワークを使用します。

DLL がロードされたら、手動でアンロードするのは一般的に悪い考えです。
非常に多くの落とし穴があります (あなたが見つけたように)。
最も簡単な解決策は、DLL を (一度ロードすると) アンロードしないことです。アプリケーションの終了時に、その OS にそれを行わせます。

于 2009-12-30T08:01:01.643 に答える