第23章 同姓同名2

 今回はオーバーロードについてです。でも、普通のオーバーロードとは違って...。


 では、今回の要点です。


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


 CIntArray には、CIntArray をコピーする関数がありません。コピーコンストラクタはあるのですが、コンストラクタなので変数を作るときにしか使えません。

 そこで、コピーをする関数を作ってみましょう。処理はコピーコンストラクタと同じなので、コピーコンストラクタからもこの関数を呼ぶようにします。

 そして、コピーするときに m_nNumOf の値を変える必要があるので、定数メンバにするのはやめます。前のソースに戻してやって下さい。

プログラム1
// IntArray.h
class CIntArray
{
    // コピー
public:
    bool Copy(const CIntArray& rother);  // 配列のコピー

    // 諸関数
private:
    void Init();     // メンバの初期化
    void Release();  // メモリの解放

    // Success 関数の名前を IsValid に変更して下さい
    bool IsValid() const;  // m_pnum の値が有効かどうか
};

// 実装の方でも Success 関数の名前を IsValid に変更して下さい
プログラム2
// IntArray.cpp
// コピーコンストラクタ
CIntArray::CIntArray(const CIntArray& rother)
{
    Init();
    Copy(rother);

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

// デストラクタ
CIntArray::~CIntArray()
{
    cout << "デストラクタが呼ばれました。"
         << "要素数は " << m_nNumOf << " でした。" << endl;

    Release();
}

// 配列のコピー
bool CIntArray::Copy(const CIntArray& rother)
{
    // 自分自身はコピーしない
    if(m_pnum == rother.m_pnum)
        return true;

    Release();  // 配列が確保されているときはメモリを解放

    if(rother.IsValid() == true)
    {
        m_pnum = new int[rother.NumOf()];
        if(m_pnum == NULL)
        {
            m_nNumOf = 0;
            return false;
        }

        // memcpy はメモリの内容をバイト単位でコピーする関数です
        memcpy(m_pnum, rother.m_pnum, rother.SizeOf());
        m_nNumOf = rother.m_nNumOf;
    }

    return true;
}

// メンバの初期化
void CIntArray::Init()
{
    m_pnum   = NULL;
    m_nNumOf = 0;
}

// メモリの解放
// 配列が確保されているときだけメモリを解放します
// 解放した後はメンバを初期化します
void CIntArray::Release()
{
    if(IsValid() == true)
    {
        delete [] m_pnum;
        Init();
    }
}

 メモリが確保されたままコピーしてしまうとメモリリークになってしまうので、メモリを解放してからコピーします。メモリを解放する関数は Release としてまとめました。デストラクタからもこの関数を呼ぶようにしました。

 そして、メモリを解放したあとはポインタに NULL を入れておきます。これをしておかないと Release 関数をもう一度呼んだときにまた delete しようとしてしまいます。実際にそういう使い方は今のところしませんが、NULL を入れておくのが筋です。

 あとは、m_nNumOf も0にしておくと安全ですね。CheckIndex で必ず引っかかるようになり、メモリが確保されていないときはそのオブジェクトを使えないようにすることができます。

 ということで、メンバの初期化関数を作っておきました。コピーコンストラクタでも呼ばれるので、関数にしておくと便利です。コピーコンストラクタでこの初期化をしていないと、Copy 関数の初めにある Release 関数で if(IsValid() == true) が真になってしまいますね。(偽になることはめったにないでしょう。)

 これで無事コピー関数が作れました。


 でも、コピーというのは代入することでもできてほしいと思いませんか?

array1 = array2;

という感じです。感覚的に分かりやすいですよね。

 ということで、こういう時のために演算子の動作を定義することができます。これを演算子のオーバーロードと呼びます。オーバーロードと言うからには、引数の型が違えば同じ演算子の動作をいくつも定義することができます。

 上の代入では、CIntArray のオブジェクトへCIntArray のオブジェクトが代入されています。そういうときはこうします。

プログラム1
// IntArray.h
class CIntArray
{
    // コピー
public:
    void operator =(const CIntArray& rother); // = 演算子によるコピー
};
プログラム2
// IntArray.cpp
// = 演算子によるコピー
void CIntArray::operator =(const CIntArray& rother)
{
    Copy(rother);
}

 なにやらメンバ関数を作っています。関数の名前は operator =です。

 このように、operator <演算子>という名前の関数を作れば演算子の動作を定義できるのです。

 = 演算子は2項演算子です。2項演算子には左項と右項があります。例えば、a+bではaが左項でbが右項になります。

 メンバ関数で2項演算子をオーバーロードした場合は左項が自分自身になります。そして、右項が引数になります。1項演算子(符号演算子とか)では演算される項がそのまま自分自身になります。

 例えば、上の array1 = array2; では、array1 が自分自身になって、array2 が引数になります。これは array1.Copy(array2); と同じ関係ですね。なので、演算子の実装は普通に Copy を呼ぶだけで構いません。

 また、インクリメント、デクリメント演算子では、前置、後置の違いがあります(第3部第7章参照)。前置の場合は普通に引数なしで定義すればいいのですが、後置の場合は前置と区別するために int 型の引数をとります。この引数はオーバーロードのためのダミーでしかなく、この引数の使い道は他にありません。

void operator++(   ) { ... }  // 前置インクリメント
void operator++(int) { ... }  // 後置インクリメント

 また、演算子をオーバーロードするといっても、別に演算を行う必要はありません。例えば、「右項にある数値を表示する」なんてこともできるわけです。

 これはまさに cout のやっていることです。そうです。cout はクラスのオブジェクトだったのです。そして、<< 演算子をオーバーロードして、型に合わせて表示を行っているのです

 戻り値に自分自身を返すようにすれば、さらに次の << で表示が行えるわけです。自分自身を返す方法については、次々回に話すことになると思います。


 では、今回の要点です。


 次回も演算子のオーバーロードをやっていきます。それでは。


第22章 メンバ定数 | 第24章 同姓同名3

Last update was done on 2004.1.26

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