インスタンス/参照カウンターを使用しないシングルトンオブジェクトは、C++のメモリリークと見なす必要がありますか?
カウントがゼロのときにシングルトンインスタンスの明示的な削除を要求するカウンターがない場合、オブジェクトはどのように削除されますか?アプリケーションの終了時にOSによってクリーンアップされていますか?そのシングルトンがヒープにメモリを割り当てていた場合はどうなりますか?
一言で言えば、Singeltonのデストラクタを呼び出す必要がありますか、それともアプリケーションの終了時にクリーンアップされることを信頼できますか?
オペレーティングシステムによってクリーンアップされていることを信頼できます。
とはいえ、デストラクタではなくファイナライザを使用してガベージコレクションされた言語を使用している場合は、シングルトンを直接クリーンにシャットダウンできる適切なシャットダウン手順が必要になることがあります。これにより、システムリソースを使用していない場合に、重要なリソースを解放できます。アプリケーションを終了するだけで正しくクリーンアップされます。これは、ファイナライザーがほとんどの言語で一種の「ベストエフォート」ベースで実行されるためです。一方、この種の信頼性を必要とするリソースはごくわずかです。ファイルハンドル、メモリなどはすべて、関係なくすべてOSに正常に戻ります。
ファイナライザではなく実際のデストラクタを使用してc ++などの言語で遅延割り当てされた(つまり、トリプルチェックロックイディオムを使用した)シングルトンを使用している場合、プログラムのシャットダウン中に呼び出されるデストラクタに依存することはできません。単一の静的インスタンスを使用している場合、デストラクタは、ある時点でmainが完了した後に実行されます。
とにかく、プロセスが終了すると、すべてのメモリがオペレーティングシステムに戻ります。
よくあることですが、「状況によって異なります」。名前に値するオペレーティングシステムでは、プロセスが終了すると、プロセス内でローカルに使用されていたすべてのメモリとその他のリソースが解放されます。あなたは単にそれについて心配する必要はありません。
ただし、シングルトンが独自のプロセス外のライフタイムを持つリソース(ファイル、名前付きミューテックスなど)を割り当てている場合は、適切なクリーンアップを検討する必要があります。
RAIIはここであなたを助けます。このようなシナリオがある場合:
class Tempfile
{
Tempfile() {}; // creates a temporary file
virtual ~Tempfile(); // close AND DELETE the temporary file
};
Tempfile &singleton()
{
static Tempfile t;
return t;
}
...そうすれば、アプリケーションが終了しても、一時ファイルが閉じられて削除されるので安心できます。 ただし、これはスレッドセーフではなく、オブジェクトの削除順序が期待または必要な順序ではない場合があります。
ただし、シングルトンがこのように実装されている場合
Tempfile &singleton()
{
static Tempfile *t = NULL;
if (t == NULL)
t = new Tempfile();
return *t;
}
...その後、別の状況になります。一時ファイルで使用されているメモリは再利用されますが、デストラクタが呼び出されないため、ファイルは削除されません。
すべてのオブジェクトを明示的にクリーンアップする必要があります。クリーンアップをOSに依存しないでください。
私が通常シングルトンを使用するのは、ファイルやハードウェアリソースなどの制御をカプセル化することです。その接続を適切にクリーンアップしないと、システムリソースが簡単にリークする可能性があります。次回アプリケーションを実行するときに、前の操作によってリソースがまだロックされていると、失敗する可能性があります。もう1つの問題は、シングルトンインスタンスが所有するバッファにまだ存在する場合、ディスクへのバッファの書き込みなどのファイナライズが発生しない可能性があることです。
これはメモリリークの問題ではありません。問題は、メモリ以外のリソースをリークしている可能性があり、簡単に回復できない可能性があることです。
言語と環境はそれぞれ異なりますが、@ Aaron Fisherは、プロセスの期間中はシングルトンが存在する傾向があることに同意します。
C++の例では、典型的なシングルトンイディオムを使用します。
Singleton &get_singleton()
{
static Singleton singleton;
return singleton;
}
シングルトンインスタンスは、関数が最初に呼び出されたときに構築され、同じインスタンスでは、プログラムのシャットダウン時のグローバル静的デストラクタフェーズ中にデストラクタが呼び出されます。
共有メモリ内の割り当てを除くすべての種類の割り当ては、プロセスの終了時にオペレーティングシステムによって自動的にクリーンアップされます。したがって、シングルトンデストラクタを明示的に呼び出す必要はありません。言い換えれば、リークなし.。
さらに、マイヤーズシングルトンのような典型的なシングルトン実装は、最初の呼び出しでの初期化中にスレッドセーフであるだけでなく、アプリケーションが終了する(デストラクタが呼び出される)ときに正常に終了することも保証されます。
とにかく、アプリケーションにUNIXシグナルが送信された場合(つまり、[〜#〜] sigterm [〜#〜]または[〜#〜] sighup [〜#〜])デフォルトの動作では、静的に割り当てられたオブジェクト(シングルトン)のデストラクタを呼び出さずにプロセスを終了します。これらのシグナルのこの問題を克服するために、exitを呼び出すハンドラーを破棄するか、exitをそのようなハンドラーとして破棄することができます--signal(SIGTERM,exit);
オブジェクトをどのように作成していますか?
グローバル変数または静的変数を使用している場合、プログラムが正常に終了すると想定して、デストラクタが呼び出されます。
たとえば、プログラム
#include <iostream>
class Test
{
const char *msg;
public:
Test(const char *msg)
: msg(msg)
{}
~Test()
{
std::cout << "In destructor: " << msg << std::endl;
}
};
Test globalTest("GlobalTest");
int main(int, char *argv[])
{
static Test staticTest("StaticTest");
return 0;
}
プリントアウト
In destructor: StaticTest
In destructor: GlobalTest
アプリケーションが終了する前に、グローバルメモリ割り当てを明示的に解放するのは民間伝承です。私たちのほとんどは習慣からそれをしていると思います、そして私たちは構造について「忘れる」のはちょっと悪いと感じているからです。 Cの世界では、割り当てにはどこかに割り当て解除が必要であるという対称性の法則があります。 C++プログラマーは、RAIIを知っていて実践している場合、考え方が異なります。
例えばの古き良き時代にAmigaOSには実際のメモリリークがありました。メモリの割り当てを解除するのを忘れた場合、システムがリセットされるまで、メモリに再びアクセスできるようになることはありません。
最近、メモリリークがアプリケーションの仮想アドレス空間から忍び寄る可能性のある自尊心のあるデスクトップオペレーティングシステムを知りません。大規模なメモリ簿記がない場合、組み込みデバイスによってマイレージが異なる場合があります。
singleton は、オブジェクトの1つのインスタンスになります。これが、カウンターを必要としない理由です。アプリケーションの長さにわたって存在する場合は、デフォルトのデストラクタで問題ありません。いずれの場合も、メモリはプロセスの終了時にオペレーティングシステムによって再利用されます。
リークの定義によって異なります。アンバウンドメモリの増加は私の本のリークであり、シングルトンはアンバウンドではありません。参照カウントを提供しない場合は、意図的にインスタンスを存続させます。事故ではなく、漏れではありません。
シングルトンラッパーのデストラクタはインスタンスを削除する必要があります。これは自動ではありません。メモリを割り当てるだけでOSリソースを割り当てない場合は、意味がありません。
ガベージコレクションがないC++のような言語では、終了前にクリーンアップすることをお勧めします。これは、デストラクタフレンドクラスで行うことができます。
class Singleton{
...
friend class Singleton_Cleanup;
};
class Singleton_Cleanup{
public:
~Singleton_Cleanup(){
delete Singleton::ptr;
}
};
プログラムの開始時にクリーンアップクラスを作成し、デストラクタを終了すると、シングルトンのクリーンアップと呼ばれます。これは、オペレーティングシステムに渡すよりも冗長かもしれませんが、RAIIの原則に従っており、シングルトンオブジェクトに割り当てられたリソースによっては必要になる場合があります。
プロセスによって割り当てられ、解放(削除)されていないヒープメモリは、OSによって再利用されます。静的変数を使用するシングルトンの最も一般的な実装を使用している場合、これはアプリケーションの終了時にもクリーンアップされます。
*これは、新しいポインタを回避し、決してクリーンアップしないようにする必要があるという意味ではありません。
私はこのような問題に遭遇しました。メインスレッドが最初に終了し、静的オブジェクトを持っていても、これは機能するはずだと思います。これの代わりに:
Singleton &get_singleton() {
static Singleton singleton;
return singleton;
}
考えています
Singleton &get_singleton() {
static std::shared_ptr<Singleton> singleton = std::make_shared<Singleton>();
static thread_local std::shared_ptr<Singleton> local = singleton;
return *local;
}
したがって、メインスレッドが終了してsingleton
を使用する場合でも、各スレッドには独自のlocal
shared_ptr
があり、1つのSingleton
を存続させます。