#pragma twice

KAB-studio > プログラミング > #pragma twice > 371 Version 17.16 純粋仮想関数と抽象クラス

#pragma twice 371 Version 17.16 純粋仮想関数と抽象クラス

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

 Version 17.16
純粋仮想関数と抽象クラス

前回はポリモーフィズムの意味について説明しました
つまり、ポインタと仮想関数を組み合わせるとそのポインタが派生クラス
みたいに動く、ってことね
それがポリモーフィズム、多態性、様々な姿を見せるということです
んー、でもまだちょっと目的とか使いどころがわからないかも……
そういった所を見ていくため、特別な仮想関数、【純粋仮想関数】につい
て説明します
じゅんすいかそうかんすう?
そう。まぁでも〈純粋〉にはこだわらない方がいいかも。簡単に言うと
〈定義のない仮想関数〉のこと
定義のない……定義って、関数の本体のことだよね、 .cpp の方にある
そう
……そんなのあるわけないじゃん
それがあるんです。以下がその例

// Data.h

// CData クラス。
// 純粋仮想関数を持つので抽象クラスです。
class CData
{
public:
    // 純粋仮想関数です。
    virtual void Output() = 0;
};

// CData クラスの派生クラス。
class CDerivedData : public CData
{
    // 定義します。
    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" );
}

これが純粋仮想関数の例です。純粋仮想関数を作る場合、仮想関数の宣言
の右に【 = 0】を付けます。つまり、以下のようにすることで純粋仮想関数
になります

    virtual void Output() = 0;

純粋仮想関数は、関数の定義がありません。つまり、以下の箇所は必要な
いということです

/*
void CData::Output()
{
    OutputDebugString( "CData::Output()\n" );
}
*/

……まじっすか
マジです
でもさでもさ、関数の中身がなかったら、呼び出しようがないじゃん。そ
んなクラス不良品じゃん、使えないじゃん
そのとおり。実は、純粋仮想関数を持つクラスは【抽象クラス】といって
変数を作ることができないんです
へ?

    // 抽象クラスの変数を作ることはできません。
    CData cData;
    // コンパイルエラー:
    // error C2259: 'CData' : 
    //     抽象クラスあるいは構造体のオブジェクトが宣言されています。
    //     'CData' の宣言を確認してください。

このように、純粋仮想関数を持つクラス、つまり抽象クラスは、変数を作
ることができないわけです
あーこれなら中身のないメンバ関数が呼ばれる心配はないわね、って! 
そんなクラスなんの意味もないじゃん!! 変数が作れないクラスになんの
意味があるっていうの!
実は意味があるんです。この純粋仮想関数は派生クラスで定義を作ること
ができます
定義を作れる?
オーバーライドすることで、中身のない純粋仮想関数に中身を作ることが
できるというわけです。さっき出てきたけど以下の部分がそう

// CData クラスの派生クラス。
class CDerivedData : public CData
{
    // 定義します。
    void Output();
};


// 派生クラスの Output() メンバ関数。
// 定義しています。
void CDerivedData::Output()
{
    OutputDebugString( "CDerivedData::Output()\n" );
}

オーバーライド、つまり純粋仮想関数と同じメンバ関数を派生クラスでも
作ることで、純粋仮想関数にはなかった定義を派生クラスで作ることができ
るというわけです
なるほど、オーバーライドすることで空欄を埋めるみたいになるわけね
そうすれば、〈実体のないメンバ関数が呼ばれる〉ことはなくなるので、
変数を作ることができます

    // CDerivedData クラスの変数を作ります。
    CDerivedData cDerivedData;

…………
…………
……で、それになんの意味があるの。 CDerivedData クラス直接作っちゃ
えばいいじゃん
うん、ここまでなら意味ないかな
ということは、意味のある使い方がある、と
そう。実はここでポリモーフィズムを使うことができるんです
ポリモーフィズム、ってことは基本クラスのポインタと仮想関数
……あ、なんとなくイメージ掴めた
というわけで、以下のように〈基本クラスのポインタ〉を通して使うこと
ができます

// 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;

    // CData クラスのポインタを作ります。
    CData *pcData;
    // このポインタに cDerivedData 変数のアドレスを
    // 格納します(アップキャスト)。
    pcData = &cDerivedData;
    // このポインタを通して、 CDerivedData クラスで
    // 定義した Output() メンバ関数を呼び出します。
    pcData->Output();
    // CDerivedData::Output()

    return 0;
}

まず、抽象クラスは〈変数を作る〉ことはできないけど、〈ポインタを作
る〉ことはできます

    // CData クラスのポインタを作ります。
    CData *pcData;

これはちょっと不思議に感じるかもね
んー、でも大丈夫。次の行でアップキャストしてるでしょ

    // このポインタに cDerivedData 変数のアドレスを
    // 格納します(アップキャスト)。
    pcData = &cDerivedData;

この時ってこうなってるんだよね

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

そうそう
このイメージがあるから、ポインタなら作れる、っていうのわかるかも
そう、ポインタはそのクラスの変数そのものじゃないからね。で

    // 定義した Output() メンバ関数を呼び出します。
    pcData->Output();
    // CDerivedData::Output()

という形で仮想関数を呼び出せば仮想関数テーブル経由で呼び出されるか

 CDerivedData クラスで定義した Output() メンバ関数が呼び出される
というわけ

/*
    Preview Next Story!
*/
変数が作れないクラスがあるなんて不思議
普通に考えると意味がないものね
でもちゃんと目的がある、と
というわけで次回
< Version 17.17 関数ポインタの代わりとしてのポリモーフィズム >
につづく!
次回は本当の目的に迫ります
んーどうかなー
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。