★代入演算子オーバーロードの罠★


ここで、代入演算子の問題点について説明します。

前に説明しましたが、代入演算子は自作しなくても勝手に作られます。

構造体と同じく「=」演算子でクラスのコピーが可能です。

しかし、これには問題点もあります。

次のようなクラスがあったとします。

これまでにやった事で作っていますので、よく中身を見ながら作ってください。

<sample program cpp069-01>

#include <iostream>

class Stage {
public:

    static const int FIELD_SIZE;

    Stage();

    ~Stage();

    bool Initialize();

private:

    int *m_pField;
};

int main()
{
    Stage stage1;

    if (!stage1.Initialize()) {
        return 1;
    }

    return 0;
}

const int Stage::FIELD_SIZE = 10;

Stage::Stage() : m_pField(NULL)
{

}

Stage::~Stage()
{
    if (m_pField) {
        delete[] m_pField;
        m_pField = NULL;
    }
}

bool Stage::Initialize()
{
    m_pField = new int[FIELD_SIZE];

    if (!m_pField) {
        return false;
    }

    return true;
}

Stageクラスにはint型のポインタ変数m_pFieldがあります。

Initialize関数でint型10個分の領域を確保しました。

デストラクタ関数で領域を解放していますので、安全に扱う事が出来ています。

しかし、ここに代入演算子が絡む事で大きな問題が発生します。


上のプログラムに次のコードを追加してみましょう。

<sample program cpp069-02>

#include <iostream>

class Stage {
public:

    static const int FIELD_SIZE;

    Stage();

    ~Stage();

    bool Initialize();

private:

    int *m_pField;
};

int main()
{
    Stage stage1;

    if (!stage1.Initialize()) {
        return 1;
    }

    Stage stage2;

    stage2 = stage1;

    return 0;
}

const int Stage::FIELD_SIZE = 10;

Stage::Stage() : m_pField(NULL)
{

}

Stage::~Stage()
{
    if (m_pField) {
        delete[] m_pField;
        m_pField = NULL;
    }
}

bool Stage::Initialize()
{
    m_pField = new int[FIELD_SIZE];

    if (!m_pField) {
        return false;
    }

    return true;
}

私の環境では、実行すると終わらなくなりました・・・

実際にどのような動作をしているのか、図にしてみます。


Initialize関数が終わった状態から始めます。

           +------------+
  stage1   |            |
           |            |
           |            |
           +------------+
  m_pField |  006FF3F8 |
           +------------+

           +------------+
  006FF3F8 |            | 
           +------------+
           |            |
           +------------+
           |            |
           +------------+
           |            |
           +------------+
           |            |
           +------------+
           |            |
           +------------+
           |            |
           +------------+
           |            |
           +------------+
           |            |
           +------------+
           |            |
           +------------+

このように、10個分の領域が確保され(仮に)先頭番地は「006FF3F8番地」とします。

その先頭番地がポインタ変数「m_pField」に入っています。


ここでstage2というクラスの実体が作られます。

           +------------+
  stage2   |            |
           |            |
           |            |
           +------------+
  m_pField |           |
           +------------+

代入演算子によって、メンバ変数がコピーされます。

           +------------+             +------------+
  stage1   |            |    stage2   |            |
           |            |             |            |
           |            |             |            |
           +------------+             +------------+
  m_pField |  006FF3F8 | → m_pField |  006FF3F8 |
           +------------+             +------------+

この時点で2つのクラスの実体が「同じ領域のアドレス」を指しています。


main関数が終わる時に、stage1もstage2も解放されます。

説明は省きますが、stage2から解放されます。

stage2が解放されると、stage2のデストラクタ関数が実行され「006FF3F8番地」の領域は解放されます。

この時点でstage1のm_pFieldが指している番地は「解放済みの番地」になります。

           +------------+
  stage1   |            |
           |            |
           |            |
           +------------+
  m_pField |  006FF3F8 |
           +------------+

           +------------+
  006FF3F8 |????????????| 
           +------------+

続いてstage1が解放されます。

stage1のデストラクタ関数が実行されますが、「006FF3F8番地」は解放済みです。

ここでプログラムは不安定になり、不具合を起こしているのです。

前から何度か書いていた「ダングリングポインタ」ですね。


このようにポインタ変数を持ったクラスに対して、代入演算子を使う事は非常に危険な行為となっています。

次回はこのような場合に安全にクラスをコピーする方法を紹介します。


次へ

戻る

目次へ