C++のプログラムが次のメッセージを出して強制停止することがあります。

 pure virtual method called

原因は単純にコーディングの間違いなのですが、大きく2種類の間違い方があります。
今日はそのうちの1つで、ちょっとややこしい方を説明してみます。

といっても実は、削除したオブジェクトに対してメンバ関数を呼び出すという、よくある失敗をしているだけです。

この現象を起こす手順は、
1) 純粋仮想関数を持つクラスを
2) 最上位の基底クラスとしている派生クラスのオブジェクト(インスタンス)を
3) 削除した後に
4) そのオブジェクトに対して最上位の基底クラスの純粋仮想関数を呼出す
5) ただし、削除したオブジェクトはメモリ上に残った状態になっている
(スタックに確保したオブジェクトの場合、この状態になりやすい)


現象が発生する仕組みは次のとおりです。

オブジェクトを削除すると、そのクラスのデストラクタが実行されます。
すると、vptr(仮想関数テーブルを指すポインタ)は基底クラスのvptrへ書き換えられます。デストラクタは最上位の基底クラスに到達するまで順に実行され、最終的にvptrは最上位の基底クラスのものになります。 => 手順3)が終了した状態

ここで、確保したメモリ自体が破棄されていなければ、オブジェクト(の残りカス)にアクセスすることができます。
手順4)で最上位の基底クラスの純粋仮想関数を呼出すと、メモリに(たまたま)残っていた最上位の基底クラスのvptrを使って関数にアクセスします。すると、まさに純粋仮想関数を呼出してしまい「pure virtual method called」となります。

確保したメモリが破棄されていると、手順4)を実行したときに一般的には「Segmentation fault」が発生するのですが、同じ間違いでも「pure virtual method called」が起きるとなじみがないので、デバッグが難しくなります。


実際に現象を起こすコードのサンプルです。

 1 #include <iostream>
 2 using namespace std;
 3
 4 #define PRINT(arg) cout << arg << endl
 5 #define PRINTFUNC() cout << __PRETTY_FUNCTION__ << endl
 6
 7 class Base {
 8 public:
 9     Base() {PRINTFUNC(); /*start();*/}
10     virtual ~Base() {PRINTFUNC();}
11
12     void start() {
13         PRINT("same proc at start.");
14         doAnything();
15     }
16     virtual void doAnything() = 0;
17 };
18
19 class Proc1 : public Base {
20 public:
21     Proc1() {PRINTFUNC();}
22     virtual ~Proc1() {PRINTFUNC();}
23     void doAnything() {
24         PRINT("uniq proc by Proc1");
25     }
26     void NonVirtual() {PRINTFUNC();}
27 };
28
29 int main(int argc, char **argv)
30 {
31     Proc1 *pointer;
32     {
33         Proc1 p1;
34         p1.start();
35         pointer = &p1;
36     }
37     pointer->NonVirtual();
38     pointer->doAnything();
39     return 0;
40 }


(34423)

コメント

お気に入り日記の更新

最新のコメント

この日記について

日記内を検索