第22章 メンバ定数

 今回からは継承から離れ、普通のクラスに関する話をやっていきたいと思います。先ずは、初期化のタイミングがさっぱり分からないメンバの初期化方法について話したいと思います。


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


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


 今回から、CIntArray に話を戻します。

 IntArray.h と IntArray.cpp はきちんと残していますか? 残していない人もいると思うので、こちらに掲載しておきます。


 さて、CIntArray のメンバ m_nNumOf は配列の要素数でした。よく見ると、このメンバはコンストラクタでしか値が代入されていません

 こういう値があると const をつけたいと思うのが人情です。しかし、const のついた変数は「初期化」しかできません。コンストラクタの中のどこに書いても「代入」になってしまい、結局「初期化」できません。

例)
CIntArray::CIntArray(const int nNumOf)
{
    m_nNumOf = nNumOf;  // エラー:const 定数に代入は出来ません
    // 省略
}

 そのため、メンバを「初期化」する構文が用意されています。実はこの構文は前にも紹介したことがあります。こういう構文です。

CIntArray::CIntArray(const int nNumOf) : m_nNumOf(nNumOf)
{
    // 省略
}

 これは継承のときに使いましたね。基底クラスのコンストラクタを呼ぶ構文と同じです

 ただ、<クラス名>(<実引数リスト>) が <変数名>(<初期値>) に変わっています。でもまぁ、大した違いではないですね。

 では、こういうメンバが2つ以上あった場合はどうなるのでしょうか? それは単純で、コンマで区切ればいいだけです

例)
CTest::CTest(int a, int b) : m_a(a), m_b(b)
{
}

 「初期化」でしかできないことにはもう2つあります。参照の初期化と、コンストラクタの呼び出しです。

 参照やオブジェクトをメンバに入れているとします。

例)
class CTest
{
    int&      m_rn;
    CIntArray m_array;

public:
    CTest(int& rn, int nNumOf);
};

 こういう場合も、上と同じように初期化することが出来ます。

CTest::CTest(int& rn, int nNumOf) : m_rn(rn), m_array(nNumOf)
{
}

 下のようにすることは出来ないのです。

CTest::CTest(int& rn, int nNumOf)
{
    m_rn = rn;        // エラー:参照が初期化されていません
    m_array(nNumOf);  // エラー:コンストラクタは初期化でしか呼べません
}

 このように、メンバを「初期化」するには、コンストラクタの実装の後ろに : <メンバ名>(<初期化値>) を書くのです

 また、このようにオブジェクトをメンバに持つことを内包(コンポジション)と呼びます。継承と同じく、クラスの機能を別のクラスに持たせることができます。しかし、その方法はお互いに全然違うので、場合によって両方を使い分けます。


 では、実際に CIntArray を書き換えて、const 定数メンバに「代入」できないことを確かめてみましょう。

プログラム1
// IntArray.h
class CIntArray
{
private:
    const int m_nNumOf;

public:
    void Error();  // コンパイルエラーの出るはずな関数
};
プログラム2
// IntArray.cpp
// コンストラクタ
CIntArray::CIntArray(const int nNumOf) : m_nNumOf(nNumOf)
{
    m_pnum = new int[nNumOf];
    if(m_pnum != NULL)
        memset(m_pnum, 0, nNumOf * sizeof *m_pnum);

    cout << "コンストラクタが呼ばれました。"
         << "要素数は " << nNumOf << " です。" << endl;
}

// コピーコンストラクタ
// メモリの確保に失敗したオブジェクトが渡されたときはコピーしません
CIntArray::CIntArray(const CIntArray& rother) : m_nNumOf(rother.NumOf())
{
    if(rother.Success() == false)
        m_pnum = NULL;
    else
    {
        m_pnum = new int[rother.NumOf()];
        if(m_pnum != NULL)
            memcpy(m_pnum, rother.m_pnum, rother.SizeOf());
    }

    cout << "コピーコンストラクタが呼ばれました。" << endl;
}

// インデックスのチェック
// メモリの確保に失敗したときに m_nNumOf を0にできないので、
// アドレスもチェックするようにしました
void CIntArray::CheckIndex(const int index) const
{
    if(Success() == true && (unsigned int)index < (unsigned int)m_nNumOf)
        return;

    cout << "インデックスが不正です!" << endl
         << "値 : " << index << endl;
    exit(1);
}

// コンパイルエラーの出るはずな関数
void CIntArray::Error()
{
    m_nNumOf = 0;  // あからさまなエラーですね(笑)
}
エラーメッセージ
IntArray.cpp(74) : error C2166: 左辺値は const オブジェクトに指定されています。
             ↑ m_nNumOf = 0; の行です

 はい。コンストラクタなどのコンパイルはうまくいきましたが、Error 関数でコンパイルエラーになりました。

 このように、const 定数メンバは「初期化」はできるけれども、「代入」はできないのです。


 では、今回の要点です。


 次回も CIntArray を掘り下げていきます。cout の謎についても触れてみたいと思います。

 それでは、次回まで。


第21章 メンバの呼び分け | 第23章 同姓同名2

Last update was done on 2000.8.27

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