#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のトップページをご覧ください。