私が知っているように、サブクラスを持つように指定されたクラスは仮想デストラクタで宣言する必要があるため、ポインタを介してアクセスするときにクラス インスタンスを適切に破棄できます。
しかし、なぜそのようなクラスを非仮想デストラクタで宣言できるのでしょうか? 仮想デストラクタをいつ使用するかは、コンパイラが決定できると思います。それで、それは C++ 設計の見落としですか、それとも何か不足していますか?
私が知っているように、サブクラスを持つように指定されたクラスは仮想デストラクタで宣言する必要があるため、ポインタを介してアクセスするときにクラス インスタンスを適切に破棄できます。
しかし、なぜそのようなクラスを非仮想デストラクタで宣言できるのでしょうか? 仮想デストラクタをいつ使用するかは、コンパイラが決定できると思います。それで、それは C++ 設計の見落としですか、それとも何か不足していますか?
非仮想デストラクタを使用する特定の理由はありますか?
はい、あります。
主に、それはパフォーマンスに要約されます。仮想関数をインライン化することはできません。代わりに、呼び出す正しい関数を最初に決定し (これには実行時情報が必要です)、次にその関数を呼び出す必要があります。
パフォーマンスに敏感なコードでは、コードがない場合と「単純な」関数呼び出しの違いが違いを生む可能性があります。多くの言語とは異なり、C++ はこの違いが些細なものであるとは想定していません。
しかし、なぜそのようなクラスを非仮想デストラクタで宣言できるのでしょうか?
クラスが仮想デストラクタを必要とするかどうかを(コンパイラにとって)知るのは難しいためです。
次の場合、仮想デストラクタが必要です。
delete
ポインターで呼び出すコンパイラがクラス定義を確認すると、次のようになります。
delete
に、このクラスで呼び出すつもりであることを認識できません。多くの人は、ポリモーフィズムにはインスタンスを新しくする必要があると考えていますが、これはまったくの想像力の欠如です。
class Base { public: virtual void foo() const = 0; protected: ~Base() {} };
class Derived: public Base {
public: virtual void foo() const { std::cout << "Hello, World!\n"; }
};
void print(Base const& b) { b.foo(); }
int main() {
Derived d;
print(d);
}
この場合、破棄時にポリモーフィズムが関与しないため、仮想デストラクタに料金を支払う必要はありません。
結局は哲学の問題です。実用的な場合、C++ はデフォルトでパフォーマンスと最小限のサービスを選択します (主な例外は RTTI です)。
警告に関して。問題を特定するために利用できる 2 つの警告があります。
-Wnon-virtual-dtor
(gcc、Clang): 基本クラスのデストラクタが作成されていない限り、仮想関数を持つクラスが仮想デストラクタを宣言しない場合は常に警告しprotected
ます。これは悲観的な警告ですが、少なくとも何も見逃すことはありません。
-Wdelete-non-virtual-dtor
(Clang、gcc にも移植delete
):クラスがマークされていない限り、仮想関数を持ち、仮想デストラクタを持たないクラスへのポインタで が呼び出されるたびに警告しますfinal
。誤検知率は 0% ですが、「遅れて」(場合によっては数回) 警告が表示されます。
デストラクタがデフォルトで仮想でないのはなぜですか? http://www2.research.att.com/~bs/bs_faq2.html#virtual-dtor
ガイドライン #4: 基本クラスのデストラクタは、パブリックで仮想、または保護された非仮想のいずれかである必要があります。 http://www.gotw.ca/publications/mill18.htm
参照: http://www.erata.net/programming/virtual-destructors/
編集:重複の可能性はありますか?仮想デストラクタを使用してはいけない場合は?
あなたの質問は基本的に、「クラスに仮想メンバーがある場合、C++ コンパイラがデストラクタを強制的に仮想にしないのはなぜですか?」というものです。この質問の背後にある論理は、仮想デストラクタを派生元のクラスで使用する必要があるということです。
C++ コンパイラがプログラマの考えを超えようとしない理由はたくさんあります。
C++ は、対価を支払うという原則に基づいて設計されています。何かを仮想にしたい場合は、それを要求する必要があります。明示的に。仮想であるクラス内のすべての関数は、そのように明示的に宣言する必要があります (基本クラス バージョンをオーバーライドしない限り)。
仮想メンバーを持つクラスのデストラクタが自動的に仮想化された場合、非仮想化をどのように選択しますか? C++ には、非仮想メソッドを明示的に宣言する機能がありません。では、このコンパイラ主導の動作をどのようにオーバーライドしますか。
非仮想デストラクタを持つ仮想クラスの特定の有効な使用例はありますか? 知らない。どこかに退化したケースがあるのかもしれません。しかし、何らかの理由でそれが必要な場合、あなたの提案ではそれを言うことができません.
実際に自問する必要があるのは、仮想メンバーを持つクラスに仮想デストラクタがない場合に、より多くのコンパイラが警告を発行しない理由です。結局のところ、それが警告の目的です。
結局のところ、クラスが単に非仮想である場合、非仮想デストラクタは理にかなっているようです (注 1)。
ただし、非仮想デストラクタの他の適切な用途は見当たりません。
そして、私はその質問に感謝します。とても興味深い質問です!
編集:
注 1: パフォーマンスが重要な場合は、仮想関数テーブルを使用せずに、仮想デストラクタをまったく使用せずにクラスを使用することをお勧めします。
class Vector3
例:浮動小数点値を 3 つだけ含むa について考えてみましょう。アプリケーションがそれらの配列を格納する場合、その配列はコンパクトな方法で格納できます。
仮想関数テーブルが必要な場合、および (Java などのように) ヒープ上のストレージが必要な場合でも、配列にはメモリ内の実際の要素 "SOMEWHERE" へのポインターが含まれます。
編集2:
仮想メソッドがまったくないクラスの継承ツリーさえあるかもしれません。
なんで?
なぜなら、「仮想」メソッドを持つことが一般的で好ましいケースのように見えるかもしれませんが、それは私たち人類が想像できる唯一のケースではないからです.
その言語の多くの詳細と同様に、C++ は選択肢を提供します。提供されたオプションの 1 つを選択できます。通常は、他の人が選択したオプションを選択します。しかし、そのオプションが必要ない場合もあります。
この例では、クラス Vector3 はクラス Vector2 から継承できますが、それでも仮想関数呼び出しのオーバーヘッドはありません。思った、その例はあまり良くありません;)
ここで言及されていないもう 1 つの理由は、DLL の境界です。割り当てに使用したのと同じアロケーターを使用して、オブジェクトを解放したいからです。
メソッドが DLL 内にあるが、クライアント コードが directnew
を使用してオブジェクトをインスタンス化する場合、クライアントのアロケータを使用してオブジェクトのメモリを取得しますが、オブジェクトは DLL からの vtable で埋められます。 DLL がリンクされているアロケータを使用してオブジェクトを解放するデストラクタ。
クライアントで DLL からクラスをサブクラス化すると、DLL からの仮想デストラクタが使用されないため、問題はなくなります。