web-dev-qa-db-ja.com

C ++で純粋な仮想関数呼び出しが発生する原因は何ですか?

私はC++プログラミングクラスを教えており、よくあるC++のバグを診断する方法を理解するのに十分なエラーのクラスを見てきました。しかし、私の直感が特に良くない主要なタイプのエラーが1つあります:純粋な仮想関数の呼び出しを引き起こすプログラミングエラー最も一般的なエラーこれが原因で、基本クラスのコンストラクタまたはデストラクタから仮想関数が呼び出されていることがわかりました。学生のコードのデバッグを支援するときに注意すべき他のことはありますか?

33
templatetypedef

「これを引き起こす最も一般的なエラーは、基本クラスのコンストラクターまたはデストラクターから仮想関数を呼び出すことです。」

オブジェクトが構築されると、仮想ディスパッチテーブルへのポインタは、最初は最上位のスーパークラスを対象としており、中間クラスが構築を完了したときにのみ更新されます。したがって、独自のオーバーライド関数の実装を持つサブクラスが構築を完了するまで、誤って純粋な仮想実装を呼び出すことができます。これは、最も派生したサブクラス、またはその間のどこかにある可能性があります。

部分的に構築されたオブジェクトへのポインターをたどると発生する可能性があります(非同期またはスレッド化された操作による競合状態など)。

コンパイラーが、基本クラスへのポインターが指す実際の型を知っていると考える理由がある場合、仮想ディスパッチを合理的にバイパスする可能性があります。再解釈キャストのような未定義の動作で何かをすることでそれを混乱させるかもしれません。

破棄中は、派生クラスが破棄されるときに仮想ディスパッチテーブルを元に戻す必要があるため、純粋な仮想実装を再度呼び出すことができます。

破棄後、「ダングリング」ポインタまたは参照を介してオブジェクトを継続して使用すると、純粋仮想関数が呼び出される可能性がありますが、そのような状況での動作は定義されていません。

28
Tony Delroy

純粋な仮想呼び出しが発生する可能性があるいくつかのケースを以下に示します。

  1. ダングリングポインターの使用-ポインターが有効なオブジェクトではないため、ポインターが指す仮想テーブルは、NULLを含む可能性のある単なるランダムメモリです
  2. 悪いキャストstatic_castを間違った型(またはCスタイルのキャスト)にすると、指定したオブジェクトの仮想テーブルに正しいメソッドが含まれない可能性があります(この場合、少なくとも実際にはis仮想テーブルです)前のオプションとは異なります)。
  3. DLLがアンロードされました-保持しているオブジェクトが、再度アンロードされた共有オブジェクトファイル(DLL、sl、sl)で作成された場合、メモリをゼロにできます。
8
Motti

これは、たとえば、オブジェクトへの参照またはポインターがNULL位置を指しているときに、オブジェクト参照またはポインターを使用してクラスの仮想関数を呼び出す場合に発生する可能性があります。例えば:

std::vector <DerivedClass> objContainer;  
if (!objContainer.empty()) 
   const BaseClass& objRef = objContainer.front();  
// Do some processing using objRef and then you erase the first
// element of objContainer
objContainer.erase(objContainer.begin());   
const std::string& name = objRef.name();  
// -> (name() is a pure virtual function in base class, 
// which has been implemented in DerivedClass).

この時点では、objContainer [0]に格納されているオブジェクトは存在しません。仮想テーブルにインデックスが付けられると、有効なメモリの場所が見つかりません。したがって、「純粋な仮想関数が呼び出されました」という実行時エラーが発行されます。

0
cppcoder