第70回 プログラミングについて『継承と派生』

それでは今回はWindowsのプログラムを実際に考えてみましょう。前回お話しましたように、私は、Visual C++のプログラムの開発環境をほとんど使用していないので、コンパイルとリンクはDOSプロンプトで行なっています。
コンパイルは次のコマンドで行なっています。
cl /c /MT /GX /Og /Ot /W2 ソースファイル名 ...
/MT はマルチスレッド版のランタイムライブラリを標準ヘッダファイルから選択します。
/GX はフレーム上の破棄可能オブジェクトに属する関数のコードを生成します。
/Og /Ot は最適化のオプションです。
/W2 コンパイル時の警告レベルの設定です。
デバッガを使用するときには最適化のオプションは使わないようにして下さい。
リンクは次のコマンドで行なっています。
link /incremental:no /subsystem:windows /release
オブジェクトファイル名 [ライブラリファイル名] リソースファイル名
それではまずWindowsのプログラムに必ず1つ必要なオブジェクト CWinApp を考えてみます。前回もお話しましたようにWindowsのアプリケーションには、CWinApp から派生させたオブジェクトが必ず1つだけ必要です。これは通常のプログラムの main 関数に相当するものと考えて下さい(厳密には違うのですが、最初は深く考えない方がいいと思います)。
そうそう、以前にちょっとお話したことがありますが、継承と派生の復習をしておきましょう。C++はC言語の構造体に似ているものにクラスというものがあります。構造体はあくまでもデータの構造を表現するものですが、クラスはデータの構造の他に手続きも兼ね備えているものです。
いま次のようなクラスがあったとします。
class base_class{
public:
int i;
void put(int value);
void show();
};
メンバーには変数にはパブリック属性の int 型の i、手続きにはパブリック属性の putと show があります。このクラスを基にして新しいクラスを作成することを派生といい、基本クラスの機能を受け継ぐことを継承と言います。そこで、新しいクラスを派生してみましょう。
class new_class : public base_class{
public:
void show();
};
単純にこのようにすればいいだけなのですが、新しいクラスを派生するときに基本クラスを public にしておかないと、基本クラスの手続きを継承することができませんので注意して下さい。この例では、派生させた新しいクラスでは show を再定義しています。この再定義を基本クラスの関数をオーバーライドするとも言います。
このように定義すると、
class new_class data;
で定義されたオブジェクト data は put は base_class のものを、show は new_classのものを使用することになります。
話を戻しましょう。CWinApp クラスからアプリケーション用の新しいクラスを派生させましょう。派生の方法は先ほど説明したことと殆ど同じです。
class MyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
ここでは派生した新しいクラスを MyApp としました。CWinApp クラスから派生させるときオーバーライドできる関数は色々とありますが、最低 InitInstance はオーバーライドしなければなりません。この関数は MyApp クラスからオブジェクトが生成されたときに、そのオブジェクトを初期化するものです。実際にオブジェクトを生成する前に、このクラスの宣言に virtual という見慣れないものがあります。この宣言は仮想関数を宣言するときに使用するものです。実際には基底クラスを宣言するときにこの宣言があれば良く、派生したクラスでこの宣言をしてもコンパイラは無視してしまいます。ですので、
BOOL Initinstance();
としてもいいのですが、「仮想関数をオーバーライドしたんだぞ」と分かりやすくするために私はこのように記述しています。さてこの仮想関数ですが、名前からはとても不気味なものが出来るような気がしますが、それほど不気味ではありません。次の例を見て下さい。
#include <stdio.h>
class base_class{
public:
void show();
};
void base_class::show()
{
printf("Base class\n");
}
class new_class : public base_class{
public:
void show();
};
void new_class::show()
{
printf("New class\n");
}
void main()
{
class base_class base_data,*p;
class new_class new_data;
class base_class *p;
p=&base_data;
p->show();
p=&new_data;
p->show();
}
この例では仮想関数は使っていないものです。main 関数内で、基本クラスへのポインタを、
class base_class *p;
と定義してあり、
p=&base_data;
とした後、
p->show();
とすると、当然のことですが、
Base class
と表示されます。ここまでは理解できると思います。次に、
p=&new_data;
と、基本クラスへのポインタに派生したクラスのアドレスを代入しています。これは別にC++の文法エラーにはなりません。派生したクラスであっても、基本クラスの性格を持っていますので、その基本クラスの性格の部分を使用するという意味で許されている訳です。次に
p->show();
とすると、これも当然のことですが、
Base class
と表示されます。次の例は上の例の show() を仮想関数にしたものです(クラスの宣言に virtual を指定しただけです)。
#include <stdio.h>
class base_class{
public:
virtual void show();
};
void base_class::show()
{
printf("Base class\n");
}
class new_class : public base_class{
public:
virtual void show();
};
void new_class::show()
{
printf("New class\n");
}
void main()
{
class base_class base_data,*p;
class new_class new_data;
class base_class *p;
p=&base_data;
p->show();
p=&new_data;
p->show();
}
このプログラムを実行してみると、
Base class
New class
という結果になってしまいます。ポインタ p は基本クラスへのものであるにもかかわらず、ポインタが派生クラスを指し示すときには仮想関数のものが使用されるのです。
またまた話が外れてしまいました。元に戻りましょう。
#include <afxwin.h>
class MyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
class MyApp MyApp;
まず上のプログラムを書きます。インクルードするファイル afxwin.h はMFCクラスライブラリの色々な宣言がされている巨大なファイルです。
class MyApp MyApp;
は MyApp クラスのオブジェクトを静的に生成します。
class MyApp *MyApp=new class MyApp;
とやっても問題はありませんが、無理に面倒にしない方がいいでしょう。
このプログラムをコンパイル、リンクしてみると、
外部シンボル ""public: virtual int __thiscall MyApp::InitInstance(void)"
(?InitInstance@MyApp@@UAEHXZ)" は未解決です
というエラーになってしまいます。当然のことですが、InitInstance はまだ作成していないのです。
それでは、InitInstance をつくりましょう。
BOOL MyApp::InitInstance()
{
m_pMainWnd = new class MyWnd;
return TRUE;
}
ここでまた訳の分からないものが出てきました。
m_pMainWnd
これは、CWinApp クラスの基本クラス CWinThred のデータメンバーで、アプリケーションプログラムのメインウインドウへのポインタを保持するものです。このデータメンバーにメインになるウインドウのポインタを格納しておきます。
m_pMainWnd = new class MyWnd;
は MyWnd クラスのオブジェクトを生成し、そのポインタを格納しているのですが、このclass MyWnd はまだ宣言もしていませんので当然コンパイルエラーになります。またこの式は m_pMainWnd = new MyWnd(); としても構いません。
MyApp クラスはしばらく忘れて、MyWnd クラスを考えてみましょう。メインウインドウを作成するときは、通常 CWnd クラスから派生させたクラスのオブジェクトを使用します。 CWnd クラスはMFCクラスライブラリでのウインドウの基本になるクラスで、このクラスから派生した様々なクラスがこのライブラリには含まれています。どのクラスから派生させるかが問題になりますが、ここでは CFrameWnd から派生することにします。CFrameWnd クラスはSDIインターフェイス(単一文書インターフェイス)のウインドウの基本になるクラスで、このクラスも CWnd クラスから派生しています。
class MyWnd : public CFrameWnd
{
public:
MyWnd();
};
と MyApp クラスの initInstance を追加してコンパイル、リンクしてみると、次は、
""public: __thiscall MyWnd::MyWnd(void)"(??0MyWnd@@QAE@XZ)" は未解決です
というエラーになりますので、MyWnd クラスのコンストラクタである MyWnd を作成します。
MyWnd::MyWnd()
{
Create(NULL,"",WS_OVERLAPPEDWINDOW | WS_VISIBLE ,rectDefault);
}
この中で、Create 関数は CFrameWnd クラスのメンバー関数です。第1引数にはクラス名を指定するのですが、ここではとりあえずディフォルトを使用することにして NULLを指定しています。第2引数にはウインドウ名を指定するのですがこれもとりあえずなしとしています。第3引数にはウインドウのスタイルに基本のスタイルに加え、可視状態の属性を、第4引数にはウインドウのサイズを指定しています。ウインドウのサイズに rectDefault を指定すると、Windowsが勝手にサイズを決めてくれます。
プログラムの全文は次のようになります。
#include <afxwin.h>
class MyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
class MyWnd : public CFrameWnd
{
public:
MyWnd();
};
class MyApp MyApp;
BOOL MyApp::InitInstance()
{
m_pMainWnd = new class MyWnd;
return TRUE;
}
MyWnd::MyWnd()
{
Create(NULL,"",WS_OVERLAPPEDWINDOW | WS_VISIBLE ,rectDefault);
}
このプログラムをコンパイル、リンクすると多分エラーは発生しないと思います。そして実行してみると、新しいウインドウが1つ開きます。バックは白で、左上のボタンもディフォルトの絵柄ですが、ウインドウのサイズをマウスで変更することも、最大表示にすることも、タスクバーにアイコン化することもできると思います。
MyWnd をどのクラスから派生させるかは何を作るかで変わってきますので、いろいろと試してみて下さい。派生させるときの基本のクラスによっては上記以外に必要な操作やデータも必要ですので、MFCクラスライブラリのヘルプを参照して下さい。
今回は、とにかくウインドウを1つ作るところまでをお話しました。私がWindowsのプログラムを作ろうとしたときに、ここまで理解するのに1ヶ月ぐらいかかってしまいました。Visual C++のサンプルプログラムはなんだかすごく難しいので、最低どこまでが必要なのかが分からず苦労したのです。分かってしまえば難しいものではないのですが、意外とWindowsのプログラミングの本を見ても、最低限のコードの話は出ていないものです。
ウインドウは開きましたが、これではあまり楽しくはありませんし、ここまでが最低限の基本ですので理解して下さい。
ではまた次回。