さて、前回の CIntArray クラスを発展させ、サイズを動的に決められるようにしてみましょう。クラスを使えば、第1部第71章でさんざん注意したメモリリークも比較的簡単に防ぐことができるようになります。
では、今回の要点です。
では、いってみましょう。
前回のクラスを発展させて、「サイズを動的に決められる int 型の配列」を扱うクラスを作ってみましょう。
サイズを動的に決められる配列については、第1部の第72章と第73章で話しました。いろいろややこしい制限があって、実際に使おうと思ってもしりごみしてしまう人も多いと思います。
しかし、クラスを使えばこの制限も大したことではなくなります。それについてはおいおい話していくとしまして、とりあえずクラスの骨子を見てみましょう。
class CIntArray { // メンバ変数 private: int* m_pnum; // 動的配列 int m_nNumOf; // 配列の要素数 // コンストラクタ public: CIntArray(const int nNumOf); // メンバへのアクセス関数 public: int Get(const int index); void Set(const int index, const int value); // インデックスのチェック private: void CheckIndex(const int index); }; |
CheckIndex は ELEM(m_anum) が m_nNumOf になるだけです。Get, Set も m_anum が m_pnum になるだけです。なので、これらの実装は置いておいて、コンストラクタについて考えてみましょう。コンストラクタはどうすればいいでしょうか?
// コンストラクタ CIntArray::CIntArray(const int nNumOf) { m_pnum = new int[nNumOf]; if(m_pnum == NULL) m_nNumOf = 0; else { m_nNumOf = nNumOf; memset(m_pnum, 0, nNumOf * sizeof *m_pnum); } } |
コンストラクタで、指定しただけの配列を確保します。それが
m_pnum = new int[nNumOf];
ですね。こうすると、int 型の nNumOf 個の配列がメモリ上に確保され、そのアドレスが m_pnum に入れられます。そして、m_nNumOf にその要素数を入れておきます。この m_nNumOf は CheckIndex 関数でインデックスの確認のために使われます。
ここで、メモリの確保に失敗すると m_pnum には NULL が入れられます。そのときは m_nNumOf を0にします。そうすると、CheckIndex はつねに失敗してくれますね。
しかし、これだけだとメモリの解放がどこにもありません。このままではメモリリークし放題です。
このオブジェクトがある間はメモリを解放する必要はないですが、このオブジェクトの寿命がきたらメモリを解放する必要がありますね。となると、寿命が来る直前にいちいち解放する関数を呼ぶように注意しないといけないのでしょうか?
もちろん、そんなことはありません。解決方法が用意されています。
それはデストラクタというものです。何かコンストラクタと名前が似ていますね。
デストラクタはオブジェクトが寿命を迎えたときに自動的に呼ばれる特殊な関数です。コンストラクタが初期化をするのに対して、デストラクタは後始末をするわけです。
すなわち、クラスはコンストラクタに始まりデストラクタに終わるのです。
デストラクタは、例えば次のようになります。
class CIntArray { // メンバ変数 private: int* m_pnum; // 動的配列 int m_nNumOf; // 配列の要素数 // コンストラクタ・デストラクタ public: CIntArray(const int nNumOf); ~CIntArray(); // メンバへのアクセス関数 public: int Get(const int index); void Set(const int index, const int value); // インデックスのチェック private: void CheckIndex(const int index); }; // デストラクタ CIntArray::~CIntArray() { if(m_pnum != NULL) delete [] m_pnum; } |
何やらコンストラクタに似ていますね。ただ、関数名の頭に ~ がついています。俗に「ニョロ」だの「から」だの適当に呼ばれるこれの正式名は「ティルダ」です。インターネットを利用している人はよく目にするでしょう。C/C++言語ではNOT演算子(第1部第48章参照)としても使われます。
このように、デストラクタの名前はクラス名にティルダをつけたものです。そして、戻り値も引数もありません。戻り値や引数があったところでどうしようもありませんしね。
デストラクタでメモリの解放をするようにしておくと、変数の寿命が来たときに自動的にメモリを解放してくれます。こうしておくと、メモリリークからも解放されるのです。
では、CIntArray を使ったプログラムを作ってみましょう。
プログラム1 | プログラム3 |
---|---|
// Dstruct1.cpp #include <iostream.h> #include "IntArray.h" void Viss(const int num) { cout << "Viss : No." << num << endl; } CIntArray a(10); int main() { CIntArray b(20); Viss(1); CIntArray c(30); Viss(2); { CIntArray d(40); Viss(3); } Viss(4); return 0; } |
// IntArray.cpp #include <iostream.h> #include <memory.h> #include <process.h> #include "IntArray.h" // コンストラクタ CIntArray::CIntArray(const int nNumOf) { m_pnum = new int[nNumOf]; if(m_pnum == NULL) m_nNumOf = 0; else { m_nNumOf = nNumOf; memset(m_pnum, 0, nNumOf * sizeof *m_pnum); } cout << "コンストラクタが呼ばれました。" << "要素数は " << m_nNumOf << " です。" << endl; } // デストラクタ CIntArray::~CIntArray() { if(m_pnum != NULL) delete [] m_pnum; cout << "デストラクタが呼ばれました。" << "要素数は " << m_nNumOf << " でした。" << endl; } // メンバへのアクセス関数 int CIntArray::Get(const int index) { CheckIndex(index); return m_pnum[index]; } void CIntArray::Set(const int index, const int value) { CheckIndex(index); m_pnum[index] = value; } // インデックスのチェック void CIntArray::CheckIndex(const int index) { if((unsigned int)index < (unsigned int)m_nNumOf) return; cout << "インデックスが不正です!" << endl << "値 : " << index << endl; exit(1); } |
プログラム2 | |
// IntArray.h #ifndef __INTARRAY_H_INCLUDED__ #define __INTARRAY_H_INCLUDED__ class CIntArray { // メンバ変数 private: int* m_pnum; // 動的配列 int m_nNumOf; // 配列の要素数 // コンストラクタ・デストラクタ public: CIntArray(const int nNumOf); ~CIntArray(); // メンバへのアクセス関数 public: int Get(const int index); void Set(const int index, const int value); // インデックスのチェック private: void CheckIndex(const int index); }; #endif |
|
実行結果 | |
コンストラクタが呼ばれました。要素数は 10 です。 コンストラクタが呼ばれました。要素数は 20 です。 Viss : No.1 コンストラクタが呼ばれました。要素数は 30 です。 Viss : No.2 コンストラクタが呼ばれました。要素数は 40 です。 Viss : No.3 デストラクタが呼ばれました。要素数は 40 でした。 Viss : No.4 デストラクタが呼ばれました。要素数は 30 でした。 デストラクタが呼ばれました。要素数は 20 でした。 デストラクタが呼ばれました。要素数は 10 でした。 |
グローバル変数の a が1番初めに作られ、1番最後に寿命が来ています。
同じブロックで作られた b と c ですが、コンストラクタの呼ばれるのは宣言の位置のようです。別に問題はありませんね。そして、デストラクタは後に作られた方が先に呼ばれています。
そして、内側のブロックで作られた d ですが、Viss(3); と Viss(4); の間でデストラクタが呼ばれています。ここには } しかありませんが、ここは d の寿命の来る場所ですね。期待通りの動作が行われていることが分かります。
では、今回の要点です。
クラスの話になるとどうもプログラムが長くなりますね。でも、テキストは普段よりもかなり少ないので、惑わされないで下さいね。
Last update was done on 2000.8.6
この講座の著作権はロベールが保有しています