ここで、代入演算子の問題点について説明します。
前に説明しましたが、代入演算子は自作しなくても勝手に作られます。
構造体と同じく「=」演算子でクラスのコピーが可能です。
しかし、これには問題点もあります。
次のようなクラスがあったとします。
これまでにやった事で作っていますので、よく中身を見ながら作ってください。
<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番地」は解放済みです。
ここでプログラムは不安定になり、不具合を起こしているのです。
前から何度か書いていた「ダングリングポインタ」ですね。
このようにポインタ変数を持ったクラスに対して、代入演算子を使う事は非常に危険な行為となっています。
次回はこのような場合に安全にクラスをコピーする方法を紹介します。