#pragma twice

KAB-studio > プログラミング > #pragma twice > 365 Version 17.10 仮想関数

#pragma twice 365 Version 17.10 仮想関数

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

 Version 17.10
仮想関数

「前回はオーバーライドについて説明しました」
『基本クラスのメンバ関数を、派生クラスで上書きできるって機能だったよ
ね』
「そう、オーバーライドを使うことで基本クラスのメンバ関数を修正したり
拡張したりできるわけです」
『……でもさ、水希ちゃんが言うほど重要な機能って気がしないよーな』
「今はまだね」
『実はビックリするようなのがあったりする?』
「あるんです、実は。今回はそのための機能のひとつ、【仮想関数】につい
て説明します」
『かそうかんすう?』
「そう。仮想関数というのは、ちょっと特殊なメンバ関数です。まず、
仮想関数を使用しない場合の例から」


// Data.h

// CData クラス。
class CData
{
public:
    void Output();
};

// CData クラスの派生クラス。
class CDerivedData : public CData
{
public:
    // 基本クラスの同名メンバ関数を、オーバーライドしました。
    void Output();
};


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

#include "Data.h"

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

// 派生クラスの Output() メンバ関数。
// オーバーライドしています。
void CDerivedData::Output()
{
    OutputDebugString( "CDerivedData::Output()\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 cData;
    cData.Output();
    // CData::Output()

    // CDerivedData クラスを使用します。
    CDerivedData cDerivedData;
    cDerivedData.Output();
    // CDerivedData::Output()

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


『うん、別に普通だね』
「で、【ここにブレークポイントをセット。】でブレークポイントをセット
して、実行してみて」
『ほい。で?』
「 cDerivedData 変数を変数ウィンドウで見てみて」
『ほい。……【{...}】しかないよ』

- cDerivedData  {...}
     CData     {...}

「うん、それを確認して欲しかったんです」
『??? あ、これが仮想関数っていうのに継ながる?』
「そういうこと。では、仮想関数というものを作ってみます」
『おおー』
「って言っても、実はほんのちょっと追加するだけだけど」

// Data.h

// CData クラス。
class CData
{
public:
    // Output() メンバ関数を、仮想関数にします。
    virtual void Output();
};

// CData クラスの派生クラス。
class CDerivedData : public CData
{
public:
    // 基本クラスの同名メンバ関数を、オーバーライドしました。
    void Output();
};

『なんかほとんど変わってない……』
「追加したのは、【virtual】っていう単語」

     追加
       ↓
    virtual void Output();

「このように、メンバ関数の前に virtual をつけると、そのメンバ関数は
【仮想関数】になります」
『メンバ関数ではあるんだよね』
「そう、メンバ関数のちょっと特殊なもの、それが仮想関数」
『普通の関数は仮想関数にならないの?』
「ならないんです。それは仮想関数の仕組みが解れば分かるかな」
『そうそう、結局仮想関数ってなんなの? どういうメリットがあるの?』
「メリットは次回説明するとして、まずは仮想関数がなんなのか、という点
を説明します。まず、さっきと同じようにブレークポイントで止めて」
『ほい』
「で、変数ウィンドウで cDerivedData 変数の中を見てみて」
『って、メンバ変数は追加してないんだから変わらないんじゃ……あれ? 
なんか変なのが追加されてる!』

- cDerivedData  {...}
  - CData     {...}
    - __vfptr  0x0042c13c const  CDerivedData::`vftable'
        [0] 0x00401050 CDerivedData::Output(void)

「実は、仮想関数がひとつでもあると、この __vfptr というメンバ変数が
自動的に作られるんです」
『メンバ変数が自動的に作られる???』
「このメンバ変数は【仮想関数ポインタ】っていう特殊なポインタです。
__vfptr は Virual Function PoinTeR の略です」
『仮想関数ポインタ、そのままね』
「このポインタは、【仮想関数テーブル】っていう配列の変数 vftable を
指しています」
『!? ちょ、ちょっと待った、分からなくなったんだけど』
「図にするとこんな感じ」

┌───────┐
│ cDerivedData │
│ ┌────┐ │
│ │ cData  │ │
│ │        │ │
│ │ __vfptr→→→→→
│ └────┘ │    ↓
└───────┘    ↓
                      ↓
       ←←←←←←←←
      ↓
      ↓
    vftable
  ┌─────────────────┐
 0│CDerivedData::Output()のアドレス  │
  └─────────────────┘

「まず、基本クラスは派生クラスの中に入っている、っていうのは説明した
よね」
『 Version 17.02 ( No.357 ) で教わったよ』
「 __vfptr メンバ変数は、仮想関数のあるクラスに作られます。この
メンバ変数は配列へのポインタで、外にある vftable っていう配列の
アドレスを格納しています」
『それが vftable 、仮想関数テーブルね。でもなんか、中に関数が入ってる
けど……』
「仮想関数テーブルは、普通の配列じゃないんです。実は、仮想関数の
ポインタを格納した配列なんです」
『仮想関数、ってことはメンバ関数のポインタ!?』
「そう、 virtual を指定したメンバ関数のポインタが、この配列の中に
入れられるんです」
『?????』
「……ちょっと難しいね、というわけで続く!」

/*
    Preview Next Story!
*/
『わけわかんねー!』
「ちゃんと次回説明するから!」
『……なんか嫌な予感するなー』
「な、なに?」
『すっごく難しい話になりそうな予感』
「というわけで次回」
< Version 17.11 仮想関数テーブルって? >
『につづく!』
「だいじょうぶだよ、そんなに複雑じゃないし、それに面白いよ?」
『プログラミングが面白いわけねー!!』
「ええっ!?」
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。