次のクラスを検討してください。
struct S { ~S() = delete; };
すぐに、そして質問の目的のために、S s{};
のようなS
のインスタンスを作成することはできません。
コメントで述べたように、S *s = new S;
を実行することでインスタンスを作成できますが、削除することもできません。
したがって、削除されたデストラクタの唯一の用途は次のようなものです。
struct S {
~S() = delete;
static void f() { }
};
int main() {
S::f();
}
つまり、静的関数の束のみを公開するクラスを定義し、そのクラスのインスタンスを作成する試みを禁止します。
削除されたデストラクタの他の用途(ある場合)は何ですか?
決してdelete
dにならないか、スタックに格納される(自動ストレージ)か、別のオブジェクトの一部として格納されるオブジェクトを持っている場合、=delete
はこれらすべてを防ぎます。
struct Handle {
~Handle()=delete;
};
struct Data {
std::array<char,1024> buffer;
};
struct Bundle: Handle {
Data data;
};
using bundle_storage = std::aligned_storage_t<sizeof(Bundle), alignof(Bundle)>;
std::size_t bundle_count = 0;
std::array< bundle_storage, 1000 > global_bundles;
Handle* get_bundle() {
return new ((void*)global_bundles[bundle_count++]) Bundle();
}
void return_bundle( Handle* h ) {
Assert( h == (void*)global_bundles[bundle_count-1] );
--bundle_count;
}
char get_char( Handle const* h, std::size_t i ) {
return static_cast<Bundle*>(h).data[i];
}
void set_char( Handle const* h, std::size_t i, char c ) {
static_cast<Bundle*>(h).data[i] = c;
}
ここでは、スタック上で宣言されず、動的に割り当てられない不透明なHandle
sがあります。既知の配列からそれらを取得するシステムがあります。
上記のことは未定義の動作ではないと思います。代わりに新しいものを作成するのと同様に、Bundle
の破壊に失敗してもかまいません。
また、インターフェイスはBundle
の動作を公開する必要はありません。不透明なHandle
だけです。
現在、この手法は、コードの他の部分がすべてのハンドルがその特定のバッファーにあること、またはその有効期間が特定の方法で追跡されていることを知る必要がある場合に役立ちます。おそらく、これはプライベートコンストラクターおよびフレンドファクトリー関数で処理することもできます。
1つのシナリオは、誤った割り当て解除の防止です。
#include <stdlib.h>
struct S {
~S() = delete;
};
int main() {
S* obj= (S*) malloc(sizeof(S));
// correct
free(obj);
// error
delete obj;
return 0;
}
これは非常に初歩的ですが、特別な割り当て/割り当て解除プロセス(例:工場)に適用されます
より「c ++」スタイルの例
struct data {
//...
};
struct data_protected {
~data_protected() = delete;
data d;
};
struct data_factory {
~data_factory() {
for (data* d : data_container) {
// this is safe, because no one can call 'delete' on d
delete d;
}
}
data_protected* createData() {
data* d = new data();
data_container.Push_back(d);
return (data_protected*)d;
}
std::vector<data*> data_container;
};
デストラクタをdelete
としてマークする理由
もちろん、デストラクタが呼び出されるのを防ぐために;)
ユースケースは何ですか?
私は少なくとも3つの異なる用途を見ることができます:
後者のポイントを説明するために、Cインターフェイスを想像してください。
struct Handle { /**/ };
Handle* xyz_create();
void xyz_dispose(Handle*);
C++では、リリースを自動化するためにunique_ptr
でラップする必要がありますが、誤ってunique_ptr<Handle>
と書いた場合はどうでしょうか。実行時の災害です!
その代わりに、クラス定義を微調整できます。
struct Handle { /**/ ~Handle() = delete; };
そして、コンパイラはunique_ptr<Handle>
を止め、代わりにunique_ptr<Handle, xyz_dispose>
を正しく使用するように強制します。
2つのもっともらしいユースケースがあります。最初に(いくつかのコメントが指摘しているように)オブジェクトを動的に割り当て、delete
に失敗し、オペレーティングシステムがプログラムの最後にクリーンアップできるようにすることが許容される場合があります。
代わりに(さらに奇妙なことに)バッファーを割り当ててその中にオブジェクトを作成し、そのバッファーを削除して場所を回復することはできますが、デストラクターを呼び出そうとすることは決してありません。
_#include <iostream>
struct S {
const char* mx;
const char* getx(){return mx;}
S(const char* px) : mx(px) {}
~S() = delete;
};
int main() {
char *buffer=new char[sizeof(S)];
S *s=new(buffer) S("not deleting this...");//Constructs an object of type S in the buffer.
//Code that uses s...
std::cout<<s->getx()<<std::endl;
delete[] buffer;//release memory without requiring destructor call...
return 0;
}
_
これらのいずれも、専門家の状況を除いて、良いアイデアのようには見えません。自動的に作成されたデストラクタが何もしない場合(すべてのメンバーのデストラクタが簡単なため)、コンパイラは効果のないデストラクタを作成します。
自動的に作成されたデストラクタが重要なことを行う場合、そのセマンティクスの実行に失敗してプログラムの妥当性を損なう可能性が非常に高くなります。
プログラムでmain()
を終了させ、環境を「クリーンアップ」できるようにすることは有効な手法ですが、制約によって厳密に必要にならない限り避けるのが最善です。せいぜい、それは本物のメモリリークをマスクする素晴らしい方法です!
他の自動生成されたメンバーをdelete
する機能を備えた完全性のために、この機能が存在すると思います。
この機能を実際に実際に使用したいと思います。
静的クラス(コンストラクターなし)の概念があるため、論理的にデストラクタは必要ありません。しかし、そのようなクラスは、テンプレート化されていない限り、現代のC++ではnamespace
が(良い)場所を持たないため、より適切に実装されます。
new
を使用してオブジェクトのインスタンスを作成し、それを削除しないことは、C++シングルトンを実装する最も安全な方法です。これは、あらゆる順序の破壊の問題を回避するためです。この問題の典型的な例は、別のシングルトンクラスのデストラクタでアクセスされる「ロギング」シングルトンです。 Alexandrescuはかつて彼の古典的な"Modern C++ Design" Singleton実装における破壊の順序の問題に対処する方法に関する本のセクション全体を捧げました。
削除されたデストラクタは、Singletonクラス自体でさえ誤ってインスタンスを削除できないようにするために必要です。また、delete &SingletonClass::Instance()
のようなクレイジーな使用を防ぎます(Instance()
が参照を返す場合、必要に応じて、ポインタを返す理由はありません)。
結局のところ、これのどれも本当に注目に値するものではありません。もちろん、そもそもシングルトンを使用すべきではありません。