回答:
以下のいずれかに該当する場合は、仮想デストラクタを使用する必要はありません。
あなたが本当にメモリを切望していない限り、それを避ける特定の理由はありません。
質問に明示的に答えるため、つまり仮想デストラクタを宣言しない場合。
C ++ '98 / '03
仮想デストラクタを追加すると、クラスがPOD(プレーンな古いデータ) * から変更されたり、非PODに集約されたりする可能性があります。これにより、クラスタイプがどこかで初期化された集約である場合、プロジェクトのコンパイルが停止する可能性があります。
struct A {
// virtual ~A ();
int i;
int j;
};
void foo () {
A a = { 0, 1 }; // Will fail if virtual dtor declared
}
極端なケースでは、そのような変更は、クラスがPODを必要とする方法で使用されている、たとえば省略記号パラメーターを介してそれを渡す、またはmemcpyでそれを使用する、未定義の動作を引き起こす可能性もあります。
void bar (...);
void foo (A & a) {
bar (a); // Undefined behavior if virtual dtor declared
}
[* PODタイプは、そのメモリレイアウトに関して特定の保証があるタイプです。この規格では、PODタイプのオブジェクトから文字の配列(または符号なし文字)にコピーして再度元に戻した場合、結果は元のオブジェクトと同じになると実際に述べています。]
最新のC ++
C ++の最近のバージョンでは、PODの概念は、クラスレイアウトとその構築、コピー、破棄の間で分割されました。
省略の場合は、未定義の動作ではなくなり、実装定義のセマンティクスで条件付きでサポートされるようになりました(N3937-〜C ++ '14-5.2.2 / 7):
...重要ではないコピーコンストラクター、重要な移動コンストラクター、または重要なデストラクタを持ち、対応するパラメータがないクラス型(第9項)の潜在的に評価される引数を渡すことは、実装で条件付きでサポートされます。定義されたセマンティクス。
以外のデストラクタを宣言する=default
と、それは簡単ではありません(12.4 / 5)
...デストラクタは、ユーザーが指定しなければ簡単です...
モダンC ++に対するその他の変更により、コンストラクターを追加できるため、集約初期化問題の影響が軽減されます。
struct A {
A(int i, int j);
virtual ~A ();
int i;
int j;
};
void foo () {
A a = { 0, 1 }; // OK
}
仮想メソッドがある場合に限り、仮想デストラクタを宣言します。仮想メソッドを取得したら、ヒープ上でインスタンス化したり、基本クラスへのポインターを格納したりするのを避けるのは自分ではありません。これらはどちらも非常に一般的な操作であり、デストラクタが仮想であると宣言されていない場合、多くの場合、黙ってリソースをリークします。
delete
クラスのタイプを持つサブクラスのオブジェクトへのポインターで呼び出される可能性がある場合は常に、仮想デストラクタが必要です。これにより、コンパイラがコンパイル時にヒープ上のオブジェクトのクラスを知る必要なく、実行時に正しいデストラクタが確実に呼び出されます。たとえば、B
がのサブクラスであると仮定しますA
。
A *x = new B;
delete x; // ~B() called, even though x has type A*
コードがパフォーマンスにとって重要でない場合は、安全のために、作成するすべての基本クラスに仮想デストラクタを追加するのが妥当です。
ただし、delete
タイトなループで多くのオブジェクトを使用している場合は、仮想関数(空の関数であっても)を呼び出すことによるパフォーマンスのオーバーヘッドが顕著になることがあります。コンパイラーは通常これらの呼び出しをインライン化することができず、プロセッサーはどこに行くべきかを予測するのが困難な場合があります。これがパフォーマンスに大きな影響を与える可能性は低いですが、言及する価値があります。
すべてのC ++クラスが動的ポリモーフィズムを持つ基本クラスとしての使用に適しているわけではありません。
クラスを動的なポリモーフィズムに適したものにする場合、そのデストラクタは仮想でなければなりません。さらに、サブクラスがオーバーライドしたいと考える可能性のあるすべてのメソッド(すべてのパブリックメソッドと、内部で使用されるいくつかの保護されたメソッドを意味する可能性があります)は仮想である必要があります。
クラスが動的ポリモーフィズムに適していない場合は、デストラクタを仮想としてマークしないでください。そうすると誤解を招くおそれがあります。それは人々があなたのクラスを間違って使うことを奨励するだけです。
デストラクタが仮想であっても、動的ポリモーフィズムに適さないクラスの例を次に示します。
class MutexLock {
mutex *mtx_;
public:
explicit MutexLock(mutex *mtx) : mtx_(mtx) { mtx_->lock(); }
~MutexLock() { mtx_->unlock(); }
private:
MutexLock(const MutexLock &rhs);
MutexLock &operator=(const MutexLock &rhs);
};
このクラスの要点は、RAIIのスタックに座ることです。このクラスのオブジェクトへのポインタを渡している場合、それのサブクラスは言うまでもなく、それは間違っています。
デストラクタを仮想として宣言しないことの適切な理由は、これによりクラスが仮想関数テーブルを追加する必要がなくなり、可能な限りそれを避ける必要がある場合です。
多くの人が、安全のために、常にデストラクタを仮想として宣言することを好むことを知っています。ただし、クラスに他の仮想関数がない場合は、仮想デストラクタを使用しても意味がありません。他のクラスを派生させる他の人にクラスを渡したとしても、クラスにアップキャストされたポインタで削除を呼び出す理由はありません。そうした場合、私はこれをバグと見なします。
さて、1つの例外があります。つまり、派生オブジェクトの多態的削除を実行するためにクラスが(誤って)使用されている場合ですが、あなたまたは他の人は、仮想デストラクタが必要であることを知っています。
別の言い方をすれば、クラスに非仮想デストラクタがある場合、これは非常に明確なステートメントです。「派生オブジェクトの削除に私を使用しないでください!」
私は通常、デストラクタを仮想として宣言しますが、内部ループで使用されるパフォーマンスが重要なコードがある場合は、仮想テーブルのルックアップを避けたい場合があります。これは、衝突チェックなど、場合によっては重要です。ただし、継承を使用する場合、これらのオブジェクトを破棄する方法に注意してください。そうしないと、オブジェクトの半分だけが破棄されます。
オブジェクトのいずれかのメソッドが仮想である場合、オブジェクトの仮想テーブルルックアップが発生することに注意してください。したがって、クラスに他の仮想メソッドがある場合、デストラクタの仮想仕様を削除しても意味がありません。
パフォーマンスの答えは、私が知っている唯一の答えであり、真実である可能性があります。デストラクタを非仮想化すると本当にスピードアップすることが測定してわかった場合は、おそらくそのクラスでもスピードアップが必要なものが他にあるはずですが、この時点ではさらに重要な考慮事項があります。いつか誰かがあなたのコードが彼らに素晴らしい基本クラスを提供し、彼らに一週間の仕事を保存することを発見しようとしています。コードをベースとして使用するのではなく、コードをコピーして貼り付けて、彼らがその週の作業を行うことを確認する方がよいでしょう。重要なメソッドの一部をプライベートにして、誰からも継承できないようにすることをお勧めします。