第16章 派生と構築

 今回はコンストラクタについてのお話です。継承した場合、コンストラクタはどう呼ばれるのでしょうか?


 それでは、今回の要点です。


 では、いってみましょう。


 前回までのプログラムで不思議に思ったことはありませんでしたか? CTextFile にはコンストラクタがありませんでした。

 こういう場合は特に何もしないデフォルトコンストラクタが自動的に作られるのですが、m_pfile, m_bCopy メンバの初期化はどうなるんでしょうね?

 簡単なプログラムを作って、継承した場合にコンストラクタがどう呼ばれるかを確かめてみましょう。併せて、デストラクタについても確かめてみます。

プログラム実行結果
// Inherit1.cpp
#include <iostream.h>

class CA
{
public:
    CA()     { cout << "CA" << endl; }
    CA(int a){ cout << "CA:" << a << endl; }
    ~CA()    { cout << "~CA" << endl; }
};

class CB : public CA
{
public:
    CB() { cout << "CB" << endl; }
    ~CB(){ cout << "~CB" << endl; }
};

int main()
{
    CB b;

    return 0;
}
CA
CB
~CB
~CA

 ...これほどのものは他にないくらい簡潔なプログラムですね。これがコンストラクタ、デストラクタの呼ばれ方です。

 つまり、派生クラスのコンストラクタを呼ぶ前基底クラスのコンストラクタが呼ばれ派生クラスのデストラクタが呼ばれた後基底クラスのデストラクタが呼ばれるのです。

 このとき、呼ばれるコンストラクタはデフォルトコンストラクタです。引数がないので、当たり前といえば当たり前ですね。

 これは、こう考えるとよく分かります。CA のメンバ変数を CB で初期化するとき、CA とは別の値で初期化したいとします。そこで基底クラスのコンストラクタが後に呼ばれてしまうと、折角 CB のコンストラクタで変更を行っても、CA のコンストラクタによって元の値に書き換えられてしまいます。

 デストラクタも同じ考えで説明できます。CB で CA のメンバ変数を使った終了処理を行いたいとき、CA での方法と別の方法で処理を行いたいとします。そこで基底クラスのデストラクタが先に呼ばれてしまうと、折角 CB のデストラクタで終了処理を書いても、CA のデストラクタによって先に処理されてしまいます。


 では、ここで引数付きのコンストラクタを呼びたいときはどうすればいいのでしょうか? どうやって引数を指定するんでしょうね。

 ということで、CFile に引数付きのコンストラクタを加えてみましょう。(面倒なので、「〜は省略」というのはもう書きません。)

// File.h
class CFile
{
    // 構築と同時にファイルを開く
public:
    CFile(const char* pszPath, const char* pszFlags);
};
// File.cpp
// 構築と同時にファイルを開く
CFile::CFile(const char* pszPath, const char* pszFlags)
{
    m_pfile = NULL;
    m_bCopy = false;
    Open(pszPath, pszFlags);
}

 先ず、CTextFile でもこのコンストラクタが使えるかどうかを試してみましょう。

プログラム
// TestFile.cpp
#include <iostream.h>
#include "TextFile.h"

int main()
{
    CTestFile txt("Test.txt", "r");

    return 0;
}
エラーメッセージ
TestFile.cpp(7) : error C2661: 'CTextFile::CTextFile' : 2 個の引数を持つオーバーロードされた関数はありません。

 ...エラーが出ましたね。

 CTextFile クラスではコンストラクタは作りませんでした。その時は「特に何もしないデフォルトコンストラクタ」が自動的に作られるだけです。

 なので、以前までのプログラムではエラーは出ませんでしたが、今回のプログラムではそうはいきません。const char* を2つとるコンストラクタを使おうとしているからです。

 ということで、CTextFile にも const char* を2つとるコンストラクタを作らなければなりません。すると、コンストラクタが定義されるので、自動的に作られるデフォルトコンストラクタは作られなくなります。ということで、デフォルトコンストラクタも作ってやる必要があります。

 また、コピーコンストラクタも定義していません。その時は「メンバ変数をコピーするだけのコピーコンストラクタ」が作られます。なので、コピーコンストラクタも作ってやる必要があります。

 しかし、動作自体はそれぞれ CFile のものと同じで構いません。

 ということで、コンストラクタで基底クラスの引数付きのコンストラクタを呼ぶこともできます。それは、次のようにします。

// TextFile.h
class CTextFile : public CFile
{
    // コンストラクタ
public:
    CTextFile();                         // デフォルトコンストラクタ
    CTextFile(const char* pszPath, const char* pszFlags);
                                         // 構築と同時にファイルを開く
    CTextFile(const CTextFile& rother);  // コピーコンストラクタ
};
// TextFile.cpp
// デフォルトコンストラクタ
CTextFile::CTextFile()
{
}

// 構築と同時にファイルを開く
CTextFile::CTextFile(const char* pszPath,
                     const char* pszFlags) : CFile(pszPath, pszFlags)
{
}

// コピーコンストラクタ
CTextFile::CTextFile(const CTextFile& rother) : CFile(rother)
{
}

 コンストラクタの実装で、何やら見たことのないことをやっています。

CTextFile::CTextFile(const CTextFile& rother) : CFile(rother)

 この赤い部分が、基底クラスのコンストラクタを呼ぶコードです。

 はっきり言って、そのまんまです。コンストラクタを呼んでいるだけです。しかし、関数の中身を定義する部分にではなく、プロトタイプの後ろに、コロン (:) を挟んで書いてあります。変わってますね。

 このように、基底クラスの引数付きコンストラクタを呼びたいときは、コンストラクタを実装するときのプロトタイプの後ろに

 : <基底クラス名>(<実引数リスト>)

と書くのです。


 ところが、これをいざ実行してみるとおかしな事が起こることが分かります。CTextFile の ModifyFlags が呼ばれず、CFile のものが呼ばれてしまうのです。これについては次回に話したいと思います。


 それでは、今回の要点です。


 それでは、次回まで。


第15章 アップキャスト | 第17章 派生と構築2

Last update was done on 2001.2.21

この講座の著作権はロベールが保有しています