いつ仮想デストラクタを使用すべきではないのですか?


回答:


72

以下のいずれかに該当する場合は、仮想デストラクタを使用する必要はありません。

  • それからクラスを派生させるつもりはない
  • ヒープのインスタンス化なし
  • スーパークラスのポインタに格納する意図はありません

あなたが本当にメモリを切望していない限り、それを避ける特定の理由はありません。


25
これは良い答えではありません。「必要がない」は「すべきでない」とは異なり、「意図がない」は「不可能にする」とは異なります。
Windowsプログラマー、

5
また、基本クラスのポインタを介してインスタンスを削除する意図はありません。
Adam Rosenfield、

9
これは実際には質問の答えにはなりません。仮想dtorを使用しない正当な理由はどこですか?
mxcl 2008年

9
何もする必要がないときは、やらないほうがいいと思います。XPのシンプルデザインの原則に従っています。
9月

12
「意図がない」と言うことで、クラスがどのように使用されるかについて大きな仮定をしていることになります。ほとんどの場合(したがって、デフォルトであるべきです)の最も簡単な解決策は、仮想デストラクタを使用することであり、特定の理由がない場合にのみそれらを回避することです。だから私はまだ何が正当な理由になるのか知りたいです。
ckarras

68

質問に明示的に答えるため、つまり仮想デストラクタを宣言しない場合。

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
}

1
あなたは正しい、そして私は間違っていた、パフォーマンスだけが理由ではない。しかし、これは私が残りの部分について正しかったことを示しています。クラスのプログラマは、クラスが他の人に継承されるのを防ぐためにコードをインクルードするほうがよいです。
Windowsプログラマ、

リチャード、あなたが書いたものについてもう少しコメントしてください。わかりませんが、グーグルで見つけた唯一の貴重なポイントのようです)または、より詳細な説明へのリンクを提供できますか?
ジョンスミス

1
@JohnSmith回答を更新しました。うまくいけば、これが役立ちます。
Richard Corden 2017年

27

仮想メソッドがある場合に限り、仮想デストラクタを宣言します。仮想メソッドを取得したら、ヒープ上でインスタンス化したり、基本クラスへのポインターを格納したりするのを避けるのは自分ではありません。これらはどちらも非常に一般的な操作であり、デストラクタが仮想であると宣言されていない場合、多くの場合、黙ってリソースをリークします。


3
そして、実際には、gccに警告オプションがあり、そのケースを正確に警告します(仮想メソッドですが、仮想dtorはありません)。
CesarB 2008年

6
他の仮想関数があるかどうかに関係なく、クラスから派生した場合は、メモリリークのリスクを負うことになりますか?
Mag Roader、

1
私はマグに同意します。この仮想デストラクタまたは仮想メソッドの使用は、別の要件です。仮想デストラクタは、クラスがクリーンアップを実行する機能(メモリの削除、ファイルのクローズなど)を提供し、さらにそのすべてのメンバーのコンストラクタが呼び出されることを保証します。
user48956、

7

deleteクラスのタイプを持つサブクラスのオブジェクトへのポインターで呼び出される可能性がある場合は常に、仮想デストラクタが必要です。これにより、コンパイラがコンパイル時にヒープ上のオブジェクトのクラスを知る必要なく、実行時に正しいデストラクタが確実に呼び出されます。たとえば、Bがのサブクラスであると仮定しますA

A *x = new B;
delete x;     // ~B() called, even though x has type A*

コードがパフォーマンスにとって重要でない場合は、安全のために、作成するすべての基本クラスに仮想デストラクタを追加するのが妥当です。

ただし、deleteタイトなループで多くのオブジェクトを使用している場合は、仮想関数(空の関数であっても)を呼び出すことによるパフォーマンスのオーバーヘッドが顕著になることがあります。コンパイラーは通常これらの呼び出しをインライン化することができず、プロセッサーはどこに行くべきかを予測するのが困難な場合があります。これがパフォーマンスに大きな影響を与える可能性は低いですが、言及する価値があります。


「コードがパフォーマンスにとって重要でない場合は、安全のために、作成するすべての基本クラスに仮想デストラクタを追加するのが妥当です。」私が見るすべての回答でより強調されるべきです
csguy

5

仮想関数は、割り当てられたすべてのオブジェクトが仮想関数テーブルポインタによってメモリコストを増加させることを意味します。

したがって、プログラムが非常に多数のオブジェクトを割り当てる必要がある場合は、オブジェクトごとに追加の32ビットを節約するために、すべての仮想関数を回避する価値があります。

それ以外の場合はすべて、デバッグの悲惨さを解消して、dtorを仮想化します。


1
ただ、NIT-摘みが、これらの日のポインタは、多くの場合、64ビットの代わりに32になります
ヘッドオタク

5

すべての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のスタックに座ることです。このクラスのオブジェクトへのポインタを渡している場合、それのサブクラスは言うまでもなく、それは間違っています。


2
多態的な使用は、多態的な削除を意味しません。クラスが仮想メソッドを持ちながら仮想デストラクタを持たないユースケースはたくさんあります。ほとんどすべてのGUIツールキットで、静的に定義された典型的なダイアログボックスを検討してください。親ウィンドウは子オブジェクトを破棄し、各オブジェクトの正確なタイプを認識しますが、すべての子ウィンドウは、ヒットテスト、描画、テキストのテキストをフェッチするアクセシビリティAPIなど、さまざまな場所で多態的に使用されます-音声合成エンジンなど
Ben Voigt 2010

4
本当ですが、質問者は、仮想デストラクタを特に回避する必要がある場合を尋ねています。あなたが説明するダイアログボックスの場合、仮想デストラクタは無意味ですが、IMOは有害ではありません。基本クラスポインターを使用してダイアログボックスを削除する必要がないことを確信できるかどうかはわかりません。たとえば、将来、親ウィンドウでファクトリを使用して子オブジェクトを作成したい場合があります。したがって、仮想デストラクタを回避することの問題ではありません。仮想デストラクタを持たなくてもかまいません。導出するのに適していないクラスに仮想デストラクタがある、それは誤解ですので、しかし、有害な。
スティーブジェソップ2010

4

デストラクタを仮想として宣言しないことの適切な理由は、これによりクラスが仮想関数テーブルを追加する必要がなくなり、可能な限りそれを避ける必要がある場合です。

多くの人が、安全のために、常にデストラクタを仮想として宣言することを好むことを知っています。ただし、クラスに他の仮想関数がない場合は、仮想デストラクタを使用しても意味がありません。他のクラスを派生させる他の人にクラスを渡したとしても、クラスにアップキャストされたポインタで削除を呼び出す理由はありません。そうした場合、私はこれをバグと見なします。

さて、1つの例外があります。つまり、派生オブジェクトの多態的削除を実行するためにクラスが(誤って)使用されている場合ですが、あなたまたは他の人は、仮想デストラクタが必要であることを知っています。

別の言い方をすれば、クラスに非仮想デストラクタがある場合、これは非常に明確なステートメントです。「派生オブジェクトの削除に私を使用しないでください!」


3

多数のインスタンスを持つ非常に小さなクラスがある場合、vtableポインターのオーバーヘッドにより、プログラムのメモリ使用量に違いが生じる可能性があります。クラスに他の仮想メソッドがない限り、デストラクタを非仮想にすると、オーバーヘッドが節約されます。


1

私は通常、デストラクタを仮想として宣言しますが、内部ループで使用されるパフォーマンスが重要なコードがある場合は、仮想テーブルのルックアップを避けたい場合があります。これは、衝突チェックなど、場合によっては重要です。ただし、継承を使用する場合、これらのオブジェクトを破棄する方法に注意してください。そうしないと、オブジェクトの半分だけが破棄されます。

オブジェクトのいずれかのメソッドが仮想である場合、オブジェクトの仮想テーブルルックアップが発生することに注意してください。したがって、クラスに他の仮想メソッドがある場合、デストラクタの仮想仕様を削除しても意味がありません。


1

クラスにvtableがないことを確実に確認する必要がある場合は、仮想デストラクタも必要ありません。

これはまれなケースですが、実際に起こります。

これを行う最も一般的なパターンの例は、DirectX D3DVECTORおよびD3DMATRIXクラスです。これらは構文糖衣の関数ではなくクラスメソッドですが、これらのクラスは多くの高性能アプリケーションの内部ループで特に使用されるため、関数のオーバーヘッドを回避するために、意図的にクラスにvtableがありません。


0

基本クラスで実行され、仮想的に動作する必要がある操作では、仮想である必要があります。削除が基本クラスのインターフェースを介してポリモーフィックに実行できる場合、仮想的に動作し、仮想的でなければなりません。

クラスから派生するつもりがない場合、デストラクタは仮想である必要はありません。そして、たとえそうしたとしても、基本クラスポインタの削除が不要であれば、保護された非仮想デストラクタも同様に優れています


-7

パフォーマンスの答えは、私が知っている唯一の答えであり、真実である可能性があります。デストラクタを非仮想化すると本当にスピードアップすることが測定してわかった場合は、おそらくそのクラスでもスピードアップが必要なものが他にあるはずですが、この時点ではさらに重要な考慮事項があります。いつか誰かがあなたのコードが彼らに素晴らしい基本クラスを提供し、彼らに一週間の仕事を保存することを発見しようとしています。コードをベースとして使用するのではなく、コードをコピーして貼り付けて、彼らがその週の作業を行うことを確認する方がよいでしょう。重要なメソッドの一部をプライベートにして、誰からも継承できないようにすることをお勧めします。


多態性は確かに物事を遅くします。それを多態性が必要な状況と比較して、そうしないことを選択すると、さらに遅くなります。例:RTTIとスイッチステートメントを使用してリソースをクリーンアップし、基本クラスのデストラクタですべてのロジックを実装します。
9月

1
C ++では、ドキュメント化したクラスからの継承を停止しても、基本クラスとしての使用に適していません。注意して継承を使用するのは私の責任です。もちろん、家のスタイルガイドが別段に言っていない限り。
スティーブジェソップ

1
...デストラクタを仮想化するだけでは、クラスが基本クラスとして必ずしも正しく機能するわけではありません。したがって、仮想的に「理由」とマークするのは、その評価を行う代わりに、私のコードでは現金化できない小切手を書くことです。
スティーブジェソップ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.