仮想デストラクタを使用しなくても、それを使用する場合のコストはいくらですか?
仮想関数をクラス(継承またはクラス定義の一部)に導入するコストは、オブジェクトごとに格納される仮想ポインターの初期コストが非常に急(またはオブジェクトに依存しない)になる可能性があります。
struct Integer
{
virtual ~Integer() {}
int value;
};
この場合、メモリコストは比較的膨大です。クラスインスタンスの実際のメモリサイズは、64ビットアーキテクチャでは次のようになります。
struct Integer
{
// 8 byte vptr overhead
int value; // 4 bytes
// typically 4 more bytes of padding for alignment of vptr
};
このInteger
クラスの合計は、わずか4バイトではなく16バイトです。これらの数百万個をアレイに格納すると、最終的に16メガバイトのメモリ使用量になります。通常の8 MB L3 CPUキャッシュの2倍のサイズで、そのようなアレイを繰り返し反復すると、4メガバイトの同等の数倍も遅くなります追加のキャッシュミスとページフォールトの結果として、仮想ポインターなし。
ただし、オブジェクトごとのこの仮想ポインターコストは、仮想関数が増えても増加しません。1つのクラスに100個の仮想メンバー関数を含めることができ、インスタンスごとのオーバーヘッドは引き続き単一の仮想ポインターになります。
通常、仮想ポインタは、オーバーヘッドの観点からより直接的な懸念事項です。ただし、インスタンスごとの仮想ポインターに加えて、クラスごとのコストがかかります。仮想関数を持つ各クラスはvtable
、仮想関数呼び出しが行われたときに実際に呼び出す(仮想/動的ディスパッチ)関数のアドレスを格納するメモリを生成します。vptr
このクラス固有の点、インスタンスごとに格納されvtable
。このオーバーヘッドは通常、あまり心配ですが、それはあなたのバイナリサイズを膨らませると、このオーバーヘッドは、複雑なコードベースで千個のクラスのために不必要に支払われた場合は、ランタイム・コストのビットを追加するかもしれないが、例えばこのvtable
コストの面では、実際に比例して、より増加しないとミックス内のより多くの仮想機能。
Javaユーザー定義型は暗黙的に中央のobject
基本クラスから継承し、Javaのすべての関数は暗黙的に仮想(オーバーライド可能)であるため、パフォーマンスが重要な領域で作業するJava開発者は、この種のオーバーヘッドを非常によく理解しています(ボクシングのコンテキストで説明されることが多い)。)特に明記しない限り、本質的に。その結果、JavaはInteger
同様にvptr
インスタンスごとに関連付けられたこのスタイルのメタデータの結果として64ビットプラットフォームで16バイトのメモリを必要とする傾向があり、Javaでint
ランタイムを支払わずに単一のようなものをクラスにラップすることは通常不可能ですパフォーマンスコスト。
質問は次のとおりです。なぜC ++はデフォルトですべてのデストラクタを仮想に設定しないのですか?
C ++は、「従量制」という考え方とCから継承された多くのベアメタルハードウェア駆動の設計により、パフォーマンスを本当に優先します。vtable生成と動的ディスパッチに必要なオーバーヘッドを不必要に含めたくない関係するすべてのクラス/インスタンス。C ++のような言語を使用している主な理由の1つがパフォーマンスではない場合、C ++言語の多くは理想的なパフォーマンスよりも安全性と難易度が高いため、他のプログラミング言語の恩恵を受ける可能性があります。そのような設計を支持する主な理由。
仮想デストラクタを使用する必要がないのはいつですか?
かなり頻繁に。クラスが継承されるように設計されていない場合、クラスは仮想デストラクタを必要とせず、必要のないものに対して多分大きなオーバーヘッドを支払うだけになります。同様に、クラスが継承されるように設計されていても、ベースポインターを介してサブタイプインスタンスを削除しない場合でも、仮想デストラクターは必要ありません。その場合、安全な方法は、次のように、保護された非仮想デストラクタを定義することです。
class BaseClass
{
protected:
// Disallow deleting/destroying subclass objects through `BaseClass*`.
~BaseClass() {}
};
どの場合、仮想デストラクタを使用すべきではありませんか?
実際には、仮想デストラクタを使用する必要がある場合に対応する方が簡単です。多くの場合、コードベース内のより多くのクラスは継承用に設計されません。
std::vector
それは、このベースポインタの削除の問題になりやすいだろうとして、例えば、(、継承するように設計されておらず、一般的に継承すべきではない(非常に不安定なデザイン)std::vector
不器用に加えて、故意に仮想デストラクタを避ける)オブジェクトスライス、あなたの場合の問題派生クラスは、新しい状態を追加します。
一般に、継承されるクラスには、パブリック仮想デストラクタまたは保護された非仮想デストラクタが必要です。C++ Coding Standards
、第50章から:
50.基本クラスのデストラクターをパブリックおよび仮想、または保護された非仮想にします。削除する、または削除しない それが問題です。ベースBaseへのポインターを介した削除を許可する必要がある場合、Baseのデストラクターはパブリックで仮想でなければなりません。それ以外の場合は、保護され、非仮想でなければなりません。
C ++が暗黙的に強調する傾向があるものの1つ(設計が非常に脆くて扱いにくく、場合によっては安全でなくなる可能性があるため)は、継承が後付けとして使用するように設計されたメカニズムではないという考えです。多態性を念頭に置いた拡張性メカニズムですが、拡張性が必要な場所について先見性を必要とするメカニズムです。そのため、基本クラスは継承階層のルートとして事前に設計する必要があり、事前にそのような先見のない後付けとして後から継承するものではありません。
既存のコードを再利用するために単に継承したい場合、多くの場合、構成を強くお勧めします(複合再利用の原則)。