103

クラスの仮想デストラクタを宣言しない正当な理由はありますか? 具体的に書くのを避けるべき場合はいつですか?

4

12 に答える 12

76

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

  • そこからクラスを派生させる意図はありません
  • ヒープ上でのインスタンス化なし
  • スーパークラスへのポインターを介してアクセスして保存する意図はありません

あなたが本当に記憶に迫られていない限り、それを避ける特別な理由はありません.

于 2008-11-19T04:33:10.410 に答える
71

質問に明示的に答えるには、つまり、いつ仮想デストラクタを宣言すべきでないか。

C++ '98/'03

仮想デストラクタを追加すると、クラスがPOD (plain old data) * または集約から非 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 型のオブジェクトから char (または unsigned char) の配列にコピーして元に戻した場合、結果は元のオブジェクトと同じになると実際に述べているだけです。]

最新の C++

C++ の最近のバージョンでは、POD の概念は、クラス レイアウトとその構築、コピー、および破棄の間で分割されていました。

省略記号の場合、未定義の動作ではなくなり、実装定義のセマンティクスで条件付きでサポートされるようになりました (N3937 - ~C++ '14 - 5.2.2/7):

...対応するパラメーターを持たない、自明でないコピー コンストラクター、自明でない移動コンストラクター、または自明でないデストラクタを持つクラス型 (第 9 節) の潜在的に評価される引数を渡すことは、実装で条件付きでサポートされています。定義されたセマンティクス。

以外のデストラクタを宣言すると、=default自明ではないことを意味します (12.4/5)

... ユーザーが提供しない場合、デストラクタは自明です ...

Modern C++ へのその他の変更により、コンストラクターを追加できるため、集計の初期化の問題の影響が軽減されます。

struct A {
  A(int i, int j);
  virtual ~A ();
  int i;

  int j;
};
void foo () { 
  A a = { 0, 1 };  // OK
}
于 2008-11-19T15:09:55.740 に答える
29

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

于 2008-11-19T04:37:54.440 に答える
7

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

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

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

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

于 2008-11-19T04:54:35.297 に答える
5

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

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

他のすべての場合では、dtorを仮想化するために、デバッグの惨めさを回避できます。

于 2008-11-19T15:31:29.597 に答える
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 のスタックに座ることにあります。サブクラスは言うまでもなく、このクラスのオブジェクトへのポインターを渡している場合は、間違っています。

于 2008-11-21T14:09:30.807 に答える
4

デストラクタを仮想として宣言しない正当な理由は、これによりクラスに仮想関数テーブルが追加されないようにするためであり、可能な限りそれを避ける必要があります。

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

わかりました、1 つの例外があります。つまり、派生オブジェクトのポリモーフィック削除を実行するためにクラスが (誤って) 使用されている場合ですが、これには仮想デストラクタが必要であることを知っていることを願っています。

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

于 2016-02-17T07:12:30.203 に答える
3

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

于 2010-04-12T23:19:29.223 に答える
1

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

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

于 2008-11-19T10:52:30.847 に答える
1

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

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

これを行うパターンの最もよく知られている例は、DirectX D3DVECTOR および D3DMATRIX クラスです。これらはシンタックス シュガーの関数ではなくクラス メソッドですが、これらのクラスは多くの高性能アプリケーションの内部ループで特に使用されるため、関数のオーバーヘッドを回避するために意図的に vtable を持たないクラスです。

于 2011-05-11T22:34:19.523 に答える
0

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

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

于 2010-11-13T17:52:49.080 に答える
-8

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

于 2008-11-19T05:04:18.490 に答える