#pragma twice

KAB-studio > プログラミング > #pragma twice > 366 Version 17.11 仮想関数テーブルって?

#pragma twice 366 Version 17.11 仮想関数テーブルって?

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

 Version 17.11
仮想関数テーブルって?

前回は仮想関数について説明しました
わかんねー! なんかいみふめーな感じだったんだけど
そこで、もうちょっと詳しく説明します。まず、シンプルにして継承を使
わない場合から説明します

// Data.h

// CData クラス。
class CData
{
public:
    virtual void Output1();
    virtual void Output2();
};


// Data.cpp
#include <Windows.h>
#include <stdio.h>

#include "Data.h"

// 基本クラスの Output1() メンバ関数。
void CData::Output1()
{
    OutputDebugString( "CData::Output1()\n" );
}

// 基本クラスの Output2() メンバ関数。
void CData::Output2()
{
    OutputDebugString( "CData::Output2()\n" );
}


// Main.cpp
#include <Windows.h>
#include <stdio.h>

#include "Data.h"

int WINAPI WinMain
    ( HINSTANCE p_hInstance
    , HINSTANCE p_hPrevInstance
    , LPSTR p_pchCmdLine
    , int p_iCmdShow
    )
{
    CData cData;

    return 0;   // ここにブレークポイントをセット。
}

 CData クラスだけにして、仮想関数を2つにしました
あれ? 仮想関数って継承と関係あるんじゃないの?
実際には関係あるんだけど、まずは分かりやすくってことで。この時、 
CData メンバ変数を見てみるとこうなっています

- CData     {...}
  - __vfptr  0x0042da24 const  CData::`vftable'
    [0] 0x0040105a CData::Output1(void)
    [1] 0x0040105f CData::Output2(void)

あ、なんだか配列っぽくなった
図にするとこうなります

┌────┐
│ cData  │
│        │
│ __vfptr→→→
└────┘   ↓
               ↓
       ←←←←
      ↓
      ↓
   vftable
  ┌──────────────┐
 0│CData::Output1()のアドレス  │
  ├──────────────┤
 1│CData::Output2()のアドレス  │
  └──────────────┘

cData 変数内に、 __vfptr というメンバ変数が自動的に作られます。こ
のメンバ変数はポインタで、外にある vftable という変数のアドレスを格納
しています
で、 vftable はメンバ関数の配列になっていると
そう。 vftable は【仮想関数テーブル】って言って、仮想関数、つまり
virtual の付いたメンバ関数の、アドレスの配列になっているんです
だから、さっきの Output1() と Output2() が書いてあるってことね
その前に書いてある 0x0040105a とかが実際のアドレスです
このアドレスって、関数ポインタの時に使った関数のアドレス?
そう、 Version 13.17 ( No.253 ) で説明した、関数ポインタが配列に
なったもの、って考えると分かりやすいかも
だからそれぞれ、メンバ関数のポインタが入ってるわけね
さて、ここまでが仮想関数テーブルの基本
ってゆーかここまでじゃメリット分からないし
メリットについてはもう少し先で……次に継承とオーバーライドを使用し
た場合の説明をします。さっきのプログラムで、 Output2() メンバ関数だけ
オーバーライドします

// Data.h

// CData クラス。
class CData
{
public:
    virtual void Output1();
    virtual void Output2();
};

// CData クラスの派生クラス。
class CDerivedData : public CData
{
    // オーバーライドします。
    void Output2();
};


// Data.cpp
#include <Windows.h>
#include <stdio.h>

#include "Data.h"

// 基本クラスの Output1() メンバ関数。
void CData::Output1()
{
    OutputDebugString( "CData::Output1()\n" );
}

// 基本クラスの Output2() メンバ関数。
void CData::Output2()
{
    OutputDebugString( "CData::Output2()\n" );
}

// 派生クラスの Output2() メンバ関数。
// オーバーライドしています。
void CDerivedData::Output2()
{
    OutputDebugString( "CDerivedData::Output2()\n" );
}


// Main.cpp
#include <Windows.h>
#include <stdio.h>

#include "Data.h"

int WINAPI WinMain
    ( HINSTANCE p_hInstance
    , HINSTANCE p_hPrevInstance
    , LPSTR p_pchCmdLine
    , int p_iCmdShow
    )
{
    // CDerivedData クラスを使用します。
    CDerivedData cDerivedData;

    return 0;   // ここにブレークポイントをセット。
}

うん、 Output2() メンバ関数だけオーバーライドしてる
このとき、ブレークポイントで cDerivedData 変数の中身を見ると、以下
のようになっています


- cDerivedData  {...}
  - CData     {...}
    - __vfptr  0x0042c06c const  CDerivedData::`vftable'
        [0] 0x0040100f CData::Output1(void)
        [1] 0x00401005 CDerivedData::Output2(void)

あれ……あれれ? Output2() メンバ関数が、 CDerivedData クラスで
オーバーライドした方になってる!
図にするとこうなります

┌───────┐
│ cDerivedData │
│ ┌────┐ │
│ │ cData  │ │
│ │        │ │
│ │ __vfptr→→→→→
│ └────┘ │    ↓
└───────┘    ↓
                      ↓
       ←←←←←←←←
      ↓
      ↓
   vftable
  ┌──────────────────┐
 0│CData::Output1()のアドレス          │
  ├──────────────────┤
 1│CDerivedData::Output2()のアドレス   │
  └──────────────────┘

なんで、なんでなんで?
なぜかというと、この仮想関数テーブルには、必ずオーバーライドした
メンバ関数のアドレスが格納されるようになってるんです
オーバーライドした方……つまり、 CData::Output2() は 
CDerivedData::Output2() でオーバーライドしてるから、
CDerivedData::Output2() のアドレスだけが書かれてるってこと?
そういうこと。仮想関数テーブルには各仮想関数のアドレスが書かれるわ
けど、その際、オーバーライドしたメンバ関数があると、そのメンバ関数の
アドレスで上書きされるんです
ん、なんだかそれはオーバーライドらしいかも
というわけで、次回はいよいよポリモーフィズムについて説明します!

/*
    Preview Next Story!
*/
次回はいよいよポリモーフィズムについてです
難しい? 簡単?
面白くて、不思議
意味わかんねー!
というわけで次回
< Version 17.12 ポリモーフィズム! >
につづく!
これが面白いって思うかどうかが
人としてどうかの瀬戸際ね
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。