2013-07-12 18 views
11

にIテンプレートシングルトンを有します。 DLLのC、D及びEは、ロガーを使用して、それは次のようにアクセスされる:C++テンプレートシングルトンは、DLL AではDLL

Singleton<Logger>::instance(); 

問題は、各DLLは代わりに同じシングルトンインスタンスを使用する

Singleton<Logger>. 

の独自のコピーをインスタンス化することです。私はこの問題の解決策がexternテンプレートを使用していることを理解しています。それは、C、DおよびEは

extern template class Singleton<Logger>; 

を含める必要がDLLとDLLのBであることは含まれている必要があります

template class Singleton<Logger>; 

は、これはまだ複数のテンプレートインスタンスが作成される原因となります。私はすべてのdllにexternを入れようとしましたが、まだ動作しませんでした。すべてのdllからexternを削除しようとしましたが、それでも動作しませんでした。テンプレートシングルトンを実装する標準的な方法ではありませんか?これを行う正しい方法は何ですか?

+2

シングルトンは悪です:http://c2.com/cgi/wiki?SingletonsAreEvil dllでマイニングすると、それらは悪化します。プラグイン(リンクからではなく、動的にロードされたライブラリ)と混合すると、それはさらに狂ってしまいます。 – Bruce

+0

この質問は、複数の権利ではなく、1つのプロセスでのDLLに関するものですか? – Sergei

+0

はい、正しいです。 –

答えて

7

私にとっては、シンプトンのテンプレート定義に__declspec(dllexport)を追加するというトリックがあります。テンプレートの実装をクラス定義から分割し、実装をA DLLにのみ含めます。最後に、Singleton<Logger>::instance()を呼び出すダミー関数を作成して、テンプレートをA DLLでインスタンス化するように強制します。

だからあなたのA DLLのヘッダファイルには、あなたがこのようなシングルトンテンプレート定義:

template <class T> 
class __declspec(dllexport) Singleton { 
public: 
    static T &instance(); 
}; 

を次に、あなたのA DLLのCPPファイルにテンプレートの実装を定義し、このようSingleton<Logger>のインスタンス化を強制します:

template <class T> 
T &Singleton<T>::instance() { 
    static T _instance; 
    return _instance; 
}; 

void instantiate_logger() { 
    Singleton<Logger>::instance(); 
} 

私のコンパイラでは、少なくともどこからでもinstantiate_loggerに電話する必要はありません。ちょうどそれが存在すると、コードが強制的に生成されます。したがって、A DLLのエクスポートテーブルをこの時点でダンプすると、Singleton<Logger>::instance()のエントリが表示されます。

C DLLとD DLLでは、Singletonのテンプレート定義でヘッダーファイルをインクルードできますが、テンプレートの実装がないため、コンパイラはそのテンプレート用のコードを作成できません。これは、リンカーがSingleton<Logger>::instance()の未解決の外部について不平を言うことになりますが、それを修正するには、DLLのエクスポートライブラリにリンクするだけで済みます。

最後に、Singleton<Logger>::instance()のコードはDLL Aでのみ実装されているため、複数のインスタンスを持つことはできません。

+0

私はこれがうまくいくかどうか試してみるつもりですが、シングルトンをテンプレートにするという目的を敗北させます。要は、今まで書いたすべてのシングルトンクラスでそれを再実装することなく、シングルトンの実装を提供するということでした。もしSingletonがLoggerについて知っていなければ、私が書いたすべてのシングルトンのクラスについて知っているはずです。 –

+0

@BenjyKesslerシングルトンはロガーについて知る必要はありません - ロガーはシングルトンについて知る必要があります。誰かが、どこかで 'Singleton :: instance'実装の生成を強制する必要があります - そのための合理的な場所は、Loggerが実装されている場所(私はあなたのケースではDLL Aだと思いました)です。これは、Logger Singletonを使用するために他の人がリンクする必要があるDLLであるため、Logger実装を持つDLLと同じDLLであれば意味があります。 –

+0

@BenjyKessler私は、instantiate_logger関数からシングルトンの実装を分割すると、私の答えはもっと明確になりました(私は物事を単純にしようとしていました)。通常、私はシングルトンの定義を* singleton.h *に、シングルトンの実装を* singleton.inl *に入れます。次に、* singleton.h *を含む* logger.h *と、* singleton.inl *を含む* logger.cpp *を持ち、 'instantiate_logger'関数を定義するかもしれません。 Loggerシングルトンを使いたい人は、* logger.h *(*はすでに* singleton.h *を含んでいます)をインクルードするだけです。 –

-2

instance() 
{ 
    if (_instance == NULL) { 
    _instance = new Singleton(); 
    } 

    return _instance; 
} 

のようないくつかの条件はこれが唯一の単一のインスタンスを作成し、それが2回目のコールを得たとき、それは単に古いインスタンスを返しますしてください。

+0

これは何も起こりません。インスタンス化された4つの異なるクラスがあります。各dllに1つ。したがって、各dllは、条件付きであっても、それ自身のインスタンスを持ちます。 –

+0

プラス 'static'は条件が成し遂げるすべての作業を行います。より良い(自動解放、スレッドセーフです) – nijansen

+3

あなたの答えは、OPによって示された問題と何ら差異はありません。これは単に 'instance()'メソッドの実装が悪いことを示しています。 –

5

MSDNは

のWin32のDLLは、呼び出し元プロセスのアドレス空間にマッピングされていることを述べています。 デフォルトでは、DLLを使用する各プロセスは、すべての DLLグローバル変数と静的変数の独自のインスタンスを持ちます。あなたのDLLは、他のアプリケーションによってロードされたことの 他のインスタンスとデータを共有する必要がある場合、あなたは次のいずれかの方法 を使用することができます。

Create named data sections using the data_seg pragma. 

Use memory mapped files. See the Win32 documentation about memory mapped files. 

http://msdn.microsoft.com/en-us/library/h90dkhs0%28v=vs.80%29.aspx

+0

シングルトンを含むdllにはテンプレートクラスしかないため、コンパイルされるコードがないため実際にはDLLではありません。このコードは、外部ライブラリがSingletonをインスタンス化するときにのみコンパイルされます。テンプレートのインスタンス化を共有するために必要なデータを共有する必要はありません。 –

+3

記事を読みましたか?ここで私が思うのは、異なるアドレス空間にSingleton インスタンスを共有しようとしていることです。あるDLL内でSingleton :: instance()を呼び出すと、別のDLLでSingleton :: instance()を呼び出すと、別のアドレス空間のローカル静的変数への参照を返すため、あるアドレス空間のローカル静的変数への参照が返されます。だから私はあなたの2番目のdllがSingleton の別のインスタンスをインスタンス化すると信じているのです。これは以前のインスタンスが見えないからです。問題は非常に面白いです、そして、私はそのような問題に直面していません。 – kvv

+0

はい。それはまさに私の問題です。私は解決策が何であるか疑問に思います。結局のところ私がやろうとしていることは非常に基本的で標準的なようです。私は記事を読んで、それが役立つかどうかを調べるつもりです。私は、このようなWindows固有のソリューションを使用することにはちょっと注意しています。これをサポートする言語には何かがあるはずです。 –

3

私はrefcountedクラスを組み合わせることをお勧めしますと、あなたのLoggerクラスでエクスポートAPI:

class Logger 
{ 
public: 
    Logger() 
    { 
    nRefCount = 1; 
    return; 
    }; 

    ~Logger() 
    { 
    lpPtr = NULL; 
    return; 
    }; 

    VOID AddRef() 
    { 
    InterLockedIncrement(&nRefCount); 
    return; 
    }; 

    VOID Release() 
    { 
    if (InterLockedDecrement(&nRefCount) == 0) 
     delete this; 
    return; 
    }; 

    static Logger* Get() 
    { 
    if (lpPtr == NULL) 
    { 
     //singleton creation lock should be here 
     lpPtr = new Logger(); 
    } 
    return lpPtr; 
    }; 

private: 
    LONG volatile nRefCount; 
    static Logger *lpPtr = NULL; 
}; 

__declspec(dllexport) Logger* GetLogger() 
    { 
    return Logger::Get(); 
    }; 

コードは、いくつかの固定を必要とするが、私はあなたの基本的な考え方を与えることを試みます。

+0

シングルトン全体を取り除くことをお勧めしますか?私はそれをすることはできません。もし私ができれば、まずはテンプレートを使用しないで、全体的な問題は起こらないでしょう。リフレッシュカウントが助けになるのはなぜですか? –

+0

refcountは、ロガーの寿命をより詳細に制御することです。シングルトンは、Get()メソッド(独自のコードで置き換える必要があるシングルトンコードの代わりにコメントを入れ、同じことが適用されます最後のインスタンスを解放するときはlpPtr = NULLにクリティカルセクション内にあるはずです)。基本的に、コードはCOMオブジェクトのように動作するように再フォーマットされました。 –

+0

ref countを使用する場合は、コンストラクタを保護して、他の偶発的なインスタンス化や伝統的な狂気を防ぐことができます(デストラクタを仮想的に保護します) – Bruce

7

「正しい」方法は、シングルトンを使用しないことです。

他のすべてのコードで同じタイプのインスタンスを使用する場合は、そのコードをそのインスタンスへの参照を関数またはコンストラクタのパラメータとして指定します。

シングルトン(非テンプレート)を使用するのは、グローバル変数を使用する場合とまったく同じですので、避けてください。

テンプレートを使用すると、コードをインスタンス化する方法と、「インスタンス」にアクセスする方法をコンパイラが決定します。あなたが経験している問題は、これとDLLの静的を組み合わせたものです。

生涯の問題(シングルトンを削除するのが確実なのはいつですか?)、スレッドセーフの問題、グローバルな共有アクセスの問題など、シングルトンが悪い理由はたくさんあります。

要するに、物事のインスタンスを1つだけ必要とする場合は、そのインスタンスを1つ作成し、それを必要とするコードに渡します。

+0

本当に正しいタイプ(ネームスペースではありません)コンテキストリソースを表すため、1つのインスタンスがあります。なぜ名前空間ではないのですか?明示的な構築、明示的な破壊を必要とするため、状態を持ち、どこからでもアクセス可能でなければなりません(スレッドセーフな)。名前空間は、手動ですべてのコンテンツを構築/破棄する方法があれば理想的です。機能に依存することで、間違いなく特定の範囲内のシステムを使用することが難しくなります。ハンドルハンドル型を追加して名前空間関数を呼び出すと、より簡単な型を使用できます。 – Klaim

4

ここでは、あなたが構築できる実際的なスケッチの解決策があります。複数のテンプレートがインスタンス化されますが、すべて同じインスタンスオブジェクトを共有します。

メモリリークを回避するには、いくつかの追加コードが必要です(例:void *をboost :: any of shared_ptrなどに置き換える)。 singleton.cppで

singleton.h

#if defined(DLL_EXPORTS) 
    #define DLL_API __declspec(dllexport) 
#else 
    #define DLL_API __declspec(dllimport) 
#endif 

template <class T> 
class Singleton 
{ 
public: 
    static T &instance() 
    { 
     T *instance = reinterpret_cast<T *>(details::getInstance(typeid(T))); 
     if (instance == NULL) 
     { 
      instance = new T(); 
      details::setInstance(typeid(T), instance); 
     } 

     return *instance; 
    } 
}; 

namespace details 
{ 

DLL_API void setInstance(const type_info &type, void *singleton); 
DLL_API void *getInstance(const type_info &type); 

} 

#include <map> 
#include <string> 

namespace details 
{ 

namespace 
{ 

std::map<std::string, void *> singletons; 

} 

void setInstance(const type_info &type, void *singleton) 
{ 
    singletons[type.name()] = singleton; 
} 

void *getInstance(const type_info &type) 
{ 
    std::map<std::string, void *>::const_iterator iter = singletons.find(type.name()); 
    if (iter == singletons.end()) 
     return NULL; 

    return iter->second; 
} 

} 

私は今より良い方法を考えることができません。インスタンスは、共通の場所に格納する必要があります。

+0

私はこれをあきらめてやろうと考えていました。実際にシングルトンを使用する方法があれば、うまくいくかもしれません。 –

+1

わかりません。これにより、T型のシングルトンオブジェクトが得られます。私は、シングルトンクラスの必要性を完全に理解していないことを認めています。そのクラスは本当にシングルトンではありません。あなたのgetInstance()では、Singleton オブジェクトをインスタンス化していないので、Tオブジェクトをインスタンス化しています。クラス全体はおそらく、単純な非メソッドgetSingleton ()関数に置き換えることができます。 – kede

1

私はあなたの実装では、あなたの問題を考える:

static T _instance; 

私はstatic修飾子は、各DLLのためのあなたのTクラスのインスタンスを1したコードを作成するために、コンパイラが発生することを想定しています。 シングルトンの異なる実装を試してみてください。 Singletoneクラスで静的Tフィールドを作成しようとすることができます。または、クラス内の静的ポインタを持つSingletoneが動作するはずです。このポインタが初期化されます)私はあなたが第二のアプローチを使用することをお勧めしたい、とあなたのBのDLLに、インスタンスのための最初の呼び出し(上よりも

Singletone<Logger>::instance = nullptr; 

を指定します。そして、これはあなたの問題を解決すると思います。

PS。 mutlithreading instancingを手動で処理することを忘れないでください