#pragma twice

KAB-studio > プログラミング > #pragma twice > 370 Version 17.15 ポリモーフィズムの意味

#pragma twice 370 Version 17.15 ポリモーフィズムの意味

前のページへ 表紙・目次へ 次のページへ

 Version 17.15
ポリモーフィズムの意味

「まずは前回の復習から。前回、以下の構造のクラスを作りました」

              ┌───────────┐
              │CData クラス          │
              │                      │
              │Output() メンバ関数   │
              └───────────┘
                          △
                          │
            ┌──────┴──────┐
            │                          │
┌─────┴─────┐  ┌─────┴─────┐
│CDerivedData1 クラス  │  │CDerivedData2 クラス  │
│                      │  │                      │
│Output() メンバ関数   │  │ Output() メンバ関数  │
└───────────┘  └───────────┘

「 Output() メンバ関数は仮想関数です。また、 CDerivedData1 クラスと 
CDerivedData2 クラスでオーバーライドしています」
『うんうん』
「次に、以下の関数を用意しました」

// CData クラスのポインタを受け取って、
// Output() メンバ関数を呼び出す関数。
void CallOutput( CData *p_pcData )
{
    p_pcData->Output();
}

「この関数は CData クラスのポインタを受け取り、そのポインタを通して 
Output() メンバ関数を呼び出す、という処理を行います」
『さっきの仮想関数を呼び出すわけね』
「さて、ここで使用例を見てみます。まず CDerivedData1 クラス型の変数
を渡す場合」

    // CDerivedData1 クラスの変数を作成します。
    CDerivedData1 cDerivedData1;
    // CallOutput() 関数に渡します。
    CallOutput( &cDerivedData1 );
    // CDerivedData1::Output()

「 CDerivedData1 クラスの変数 cDerivedData1 のアドレスが、 
CallOutput() 関数に渡されます。つまり、引数の p_pcData ポインタは、
cDerivedData1 変数を参照しているわけです」

void CallOutput( CData *p_pcData )
{                         ↑
       ← cDerivedData1 のアドレスが入ってます。
      ↓
    p_pcData->Output();
}

「ここ大事だからね。 Version 17.08 ( No.363 ) で説明したように、ここ
ではアップキャストが行われているので、 p_pcData は cDerivedData1 変数
を参照しています。図にするとこうなります」

CDerivedData1: 0x00000001
┌───────┐       ┌───────┐
│ cDerivedData1│       │ p_pcData     │
│ ┌────┐ │ ←←←│              │
│ │ cData  │ │       │ 0x00000001   │
│ │        │ │       └───────┘
│ │ __vfptr→→→→→
│ └────┘ │    ↓
└───────┘    ↓
                      ↓
       ←←←←←←←←
      ↓
      ↓
   vftable
  ┌──────────────────┐
 0│CDerivedData1::Output()のアドレス   │
  └──────────────────┘

「 Output() メンバ関数は仮想関数なので、仮想関数テーブルには
オーバーライドした方が格納されます。仮想関数を呼び出す際には、
仮想関数テーブルを通して呼び出されますから」

void CallOutput( CData *p_pcData )
{
cDerivedData1 のアドレスが入ってます。
      ↓  仮想関数なので CDerivedData1 クラスの方が呼び出されます。
      ↓        ↓
    p_pcData->Output();
}

「と、 CDerivedData1 クラスの Output() メンバ関数が呼び出されます。
そのため」

    CallOutput( &cDerivedData1 );
    // CDerivedData1::Output()

「という結果になるわけです」
『ようするに、元の変数でオーバーライドしたメンバ関数が呼ばれる、って
ことよね』
「そういうこと。つまり」

    CallOutput( &cDerivedData1 );
                     ↓
           CDerivedData1 クラスの変数を
           渡しているから、CDerivedData1 クラスの
           Output() メンバ関数が呼び出される
              ↓
    // CDerivedData1::Output()

「って考えてもいいかもね」
『もうひとつの方も同じだよね』
「そう、 CDerivedData2 クラスの場合は以下のように使用します」

    // CDerivedData2 クラスの変数を作成します。
    CDerivedData2 cDerivedData2;
    // CallOutput() 関数に渡します。
    CallOutput( &cDerivedData2 );
    // CDerivedData2::Output()

「この時には引数 p_pcData には cDerivedData2 変数のアドレスが渡され
るから、図にするとこうなります」

CDerivedData2: 0x00000101
┌───────┐       ┌───────┐
│ cDerivedData2│       │ p_pcData     │
│ ┌────┐ │ ←←←│              │
│ │ cData  │ │       │ 0x00000101   │
│ │        │ │       └───────┘
│ │ __vfptr→→→→→
│ └────┘ │    ↓
└───────┘    ↓
                      ↓
       ←←←←←←←←
      ↓
      ↓
   vftable
  ┌──────────────────┐
 0│CDerivedData2::Output()のアドレス   │
  └──────────────────┘

「 CDerivedData2 クラスの変数だから、仮想関数テーブルには
CDerivedData2 クラスでオーバーライドしている Output() メンバ関数の方
が呼び出されます」
『さっきは CDerivedData1 、今度は CDerivedData2 ね』
「そうなると CallOutput() 関数はこうなります」

void CallOutput( CData *p_pcData )
{                          ↑
cDerivedData2 のアドレスが入ってます。
      ↓  仮想関数なので CDerivedData2 クラスの方が呼び出されます。
      ↓        ↓
    p_pcData->Output();
}

「その結果、」

    CallOutput( &cDerivedData2 );
    // CDerivedData2::Output()

「となるわけです」
『またさっきの考え方だと、こうってことだよね』

    CallOutput( &cDerivedData2 );
                     ↓
           CDerivedData2 クラスの変数を
           渡しているから、CDerivedData2 クラスの
           Output() メンバ関数が呼び出される
              ↓
    // CDerivedData2::Output()

「そういうこと。まとめるとこうなります」

     CallOutput( &cDerivedData1 );      CallOutput( &cDerivedData2 );
                          ↓                                 ↓
void CallOutput( CData *p_pcData ) void CallOutput( CData *p_pcData )
{                         ↓       {                         ↓
         ←←←←←←←←←                 ←←←←←←←←←
        ↓                                 ↓
    p_pcData->Output();                p_pcData->Output();
        ↓↓↓↓↓                          ↓↓↓↓↓
    CDerivedData1 クラスの             CDerivedData2 クラスの
    Output() メンバ関数が              Output() メンバ関数が
    呼び出される                       呼び出される
        ↓↓↓↓↓                          ↓↓↓↓↓
    // CDerivedData1::Output()         // CDerivedData2::Output()
}                                  }

『つまり…… CallOutput() の出力結果が、渡した変数で変わる、と』
「そういうこと! CallOutput() 関数にとって、 p_pcData 変数は 
CData クラスのポインタのように見えるけど、実際にはその派生クラスの
アドレスかもしれない」
『そして、仮想関数を呼び出せば、仮想関数テーブルを通して呼び出される
から』
「 CData クラスのじゃない、オーバーライドした方のメンバ関数が呼び出
される場合がある、つまり――」

    p_pcData->Output();  
       ↑
    CData クラスとして動作するとは限らない!

『それがポリモーフィズム、多態性、 CData クラス以外の姿を見せる、って
いう意味なのね』
「ポリモーフィズムが機能するとき、どの派生クラスの仮想関数が呼び出さ
れるかわからない、つまり多くの態度を見せるわけです」

/*
    Preview Next Story!
*/
『なるほど』
「何が?」
『〈面白いかも〉って言ってた理由がちょっと分かったかも』
「面白い?」
『ちょっとだけ、ね。でも、どんな時に使ったらいいのかはわかんないけど』
「というわけで次回」
< Version 17.16 純粋仮想関数と抽象クラス >
『につづく!』
「でも面白いんだ」
『ちょっとだけだからねっ!』
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。