継承
継承の全体的なポイントは、派生クラスのインスタンスを他の派生型の他のインスタンスと同様に扱うことができるように、多くの異なる実装間で共通のインターフェイスとプロトコルを共有することです。
C ++継承では、実装の詳細も提供されます。デストラクタを仮想としてマークする(またはマークしない)ことは、そのような実装の詳細の1つです。
関数のバインド
これで、関数、またはコンストラクターやデストラクターなどの特殊なケースが呼び出されると、コンパイラーはどの関数の実装を意味するかを選択する必要があります。次に、この意図に従ったマシンコードを生成する必要があります。
これを実行する最も簡単な方法は、コンパイル時に関数を選択し、値に関係なくそのコードが実行されるときに常にその関数のコードを実行するように十分なマシンコードを出力することです。これは、継承を除いてはうまく機能します。
関数(コンストラクタまたはデストラクタを含む任意の関数)を含む基本クラスがあり、コードがその関数を呼び出す場合、これはどういう意味ですか?
あなたが呼び出された場合、あなたの例から考えると、initialize_vector()
コンパイラをあなたが本当にで見つかった実装を呼び出すためのものかどうかを判断しなければならないBase
、または実装がで見つかりましたDerived
。これを決定するには2つの方法があります。
- 1つ目は、
Base
型から呼び出したため、の実装を意味することを決定することですBase
。
- 2つ目は、
Base
型指定された値に格納されている値のランタイムタイプがである可能性があるためBase
、またはDerived
呼び出すときに(呼び出すたびに)実行時に決定を行う必要があることを決定することです。
この時点でコンパイラーは混乱しており、両方のオプションは等しく有効です。これがvirtual
ミックスに入ったときです。このキーワードが存在する場合、コンパイラはオプション2を選択し、コードが実際の値で実行されるまで、可能なすべての実装間の決定を遅らせます。このキーワードがない場合、コンパイラはオプション1を選択します。これは、それ以外の点では通常の動作であるためです。
コンパイラーは、仮想関数呼び出しの場合、依然としてオプション1を選択する場合があります。しかし、これが常に事実であることを証明できる場合にのみ。
コンストラクターとデストラクター
それでは、なぜ仮想コンストラクタを指定しないのでしょうか?
より直感的に、コンパイラはDerived
and のコンストラクタの同一の実装をどのように選択しDerived2
ますか?これは非常に簡単ですが、できません。コンパイラーが実際に意図したことを学習できる既存の値はありません。それはコンストラクタの仕事であるため、既存の値はありません。
それでは、なぜ仮想デストラクタを指定する必要があるのでしょうか?
より直感的にどのようにコンパイラがの実装の間で選択するだろうBase
とDerived
?これらは単なる関数呼び出しであるため、関数呼び出しの動作が発生します。仮想デストラクタが宣言されていない場合、コンパイラはBase
、ランタイムタイプの値に関係なく、デストラクタに直接バインドすることを決定します。
多くのコンパイラでは、派生クラスがデータメンバーを宣言せず、他の型から継承しない場合、の動作は~Base()
適切になりますが、保証されません。それは、まだ発火していない火炎放射器の前に立っているような、偶然によってのみ機能します。しばらく元気です。
C ++でベース型またはインターフェイス型を宣言する唯一の正しい方法は、仮想デストラクタを宣言することです。そのため、その型の型階層の特定のインスタンスに対して正しいデストラクタが呼び出されます。これにより、インスタンスの知識が最も多い関数がそのインスタンスを正しくクリーンアップできます。
~derived()
vecのデストラクタに委任することを提供します。あるいは、unique_ptr<base> pt
派生デストラクタを知っていると仮定しています。仮想メソッドがなければ、これは当てはまりません。unique_ptrには、ランタイム表現を持たないテンプレートパラメーターである削除関数が与えられる場合がありますが、この機能はこのコードには使用できません。