C++言語解説:2-1.クラス
2002-07-07

[概要] クラスのメンバ、アクセス指定、スコープ解決演算子、コンストラクタ、デスト
    ラクタ、インライン関数、コンストラクタのオーバーロード、コピーコンストラ
    クタについて学習します。

[構成]・クラスの基礎
     * クラスの考え方と基本
     * クラスメンバへのアクセス
   ・コンストラクタとデストラクタ
     * コンストラクタ
     * 仮引数付きコンストラクタ
     * コンストラクタのオーバーロードとデフォルト引数
     * デストラクタ
   ・オブジェクトの配列
     * オブジェクトの配列と初期化
   ・オブジェクトへのポインタと参照
     * ポインタを介したアクセス
     * 参照によるアクセス
   ・インライン関数
     * インライン関数の意味
     * クラス内部のインライン関数定義
   ・コピーコンストラクタ
     * オブジェクトをコピーする
     * 関数にオブジェクトを渡す
     * 関数からオブジェクトを返す
     * コピーコンストラクタ


クラスの基礎

 クラスの考え方と基本
 
  ・クラス(class)はオブジェクト指向プログラミングの軸となるものです。クラスは
   ブジェクトの特性と振る舞いを記述する仕組みを持ちます。

  
  ・C/C++では変数の集まりを構造体で記述できますが、この構造体のメンバに関数を定
   義できるようにした
ものがC++のクラスです。従って構造体と同様にクラスも型であり
   変数を宣言することでオブジェクト(実体)を作成します。
 
  ・List1にクラスの例を示します。

   [List1.クラスの例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class Counter {
      private:
        int mi_count;   //カウンタ値
      public:
        void Init();   //初期化
        void CountUp();  //カウントUp
        void CountDown(); //カウントDown
        int GetCount();  //カウンタ値取得
    };
    
    void Counter::Init() {  //メンバ関数定義
      mi_count = 0;
      return;
    }
    
    void Counter::CountUp() {
      mi_count++;
      return;
    }
    
    void Counter::CountDown() {
       mi_count--;
       return;
    }
    
    int Counter::GetCount() {
      return mi_count;
    }
    
    int main()
    {
      Counter counter_a; //Counter型オブジェクト定義
      counter_a.Init();
      
      counter_a.CountUp();
      counter_a.CountUp();
      counter_a.CountDown();
      counter_a.CountUp();
      
      cout << counter_a.GetCount();
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     2
   └───────────────────────────────────┘
  
  ・まずList1の4〜12行を見てみます。
   ┌───────────────────────────────────┐
    class Counter {
      private:
        int mi_count;   //カウンタ値
      public:
        void Init();   //初期化
        void CountUp();  //カウントUp
        void CountDown(); //カウントDown
        int GetCount();  //カウンタ値取得
    };
   └───────────────────────────────────┘
   
  ・この部分は、クラスの宣言を行っています。変数mi_countについては、構造体のメ
   ンバ変数と同じですが、その前に private: 指定子があるので、メンバ変数
   mi_countにはクラスの外部から直接アクセスすることはできません。
  
  ・public: 指定子の後に置かれている
     void Init();
   等はメンバ関数(member function)と呼ばれます。public: 指定子の後に置かれてい
   るメンバ変数及び、メンバ関数にはクラスの外部からアクセスできます。
  
  ・private: や public: をアクセス指定子(access modifier)と呼びます。アクセス指
   定子を省略したメンバは private: 扱いとなります。その他の指定子もありますが、
   それは継承(2.3章)で扱います。
  
  ・メンバ関数の定義例はList1の14〜17行です。
   ┌───────────────────────────────────┐
    void Counter::Init() {
      mi_count = 0;
      return;
    }
   └───────────────────────────────────┘
  
  ・クラスのメンバ関数は、所属クラスを示すために
     戻値型 クラス名::関数名 {・・・}
   と記述します。::をスコープ解決演算子(scope resolution operator)と呼びます。
   メンバ関数は、クラスのメンバ変数へアクセスすることができます。
  
  ・メンバ関数を使用するにはメンバ変数と同様に、ドット演算子(.)でオブジェクトに
   関連づけて呼び出します。

     Counter counter_a; //オブジェクト定義
     counter_a.Init(); //メンバ関数呼び出し


 クラスメンバへのアクセス
 
  ・private: で宣言したメンバには、クラスの外部から直接アクセスすることはできま
   せん。例えば、List1のCounterクラスで
     Counter counter_a;   //オブジェクト定義
     counter_a.mi_count = 5; //privateメンバに直接アクセス
   を行うとコンパイルエラーになります。
  
  ・ただしクラスのメンバ関数は、privateのメンバにもアクセスすることができます。
   つまりprivateなメンバはクラス内部で使用できますが、クラス外部からは見えない
   ものになります。
  
  ・メンバ変数/関数をメンバ関数内で使用する場合、ドット演算子を付ける必要はあり
   ません。メンバ関数内でオブジェクト名を省略した場合、コンパイラは「自分自身」
   であると解釈
します。(*1)
  
  ・Counterクラスは一般的な「カウンタ」を想定したクラスです。実際のカウンタ値を
   持つmi_count変数はprivateメンバにしています。これは、カウンタ値が自由に設定
   できると、結果が正しいと言えなくなってしまうからです。
  
  ・筆者が想定したカウンタの動きは以下の4種類です。それぞれにメンバ関数を設定し、
   このメンバ関数を介する以外にカウンタ値を操作できないように設計しています。
     リセット     : Init()
     カウントアップ  : CountUp()
     カウントダウン  : CountDown()
     カウント値取得  : GetCount()
  
  ・このように必要に応じてデータへのアクセスを制限する考え方を隠蔽(hiding)と呼
   びます。適切なデータの隠蔽を行うことで、クラスでカプセル化したオブジェクト
   の独立性を高め、結果的に保守や変更に強いクラスを作成することができます。


コンストラクタとデストラクタ

 コンストラクタ
 
  ・List1のCounterクラスで、カウンタ値を初期化するには、Init()を実行する必要が
   あります。しかしカウンタ値の初期化は、オブジェクト定義時に行いたいものです。
   このようなオブジェクト定義時の初期化処理はコンストラクタ(constructor)関数
   実行することができます。
  
  ・コンストラクタの関数名はクラス名と同じです。そしてコンストラクタは初期化を
   目的としているので戻値型を持ちません。List2にコンストラクタを含んだ例を示し
   ます。
 
   [List2.コンストラクタを含んだクラスの例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class Counter {
      private:
        int mi_count;   //カウンタ値
      public:
        Counter();    //コンストラクタ
        void Init();   //初期化
        void CountUp();  //カウントUp
        void CountDown(); //カウントDown
        int GetCount();  //カウンタ値取得
    };
    
    Counter::Counter() {   //コンストラクタ
      mi_count = 0;
      return;
    }
    
       :  省略
       :  Init(), CountUp(), CountDown(), GetCount()
       :  はList1と同様
    
    int main()
    {
      Counter counter_a;  //オブジェクト定義
                 //コンストラクタでカウンタ初期化
       :  省略
       :  List1と同様
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     2
   └───────────────────────────────────┘
  
  ・コンストラクタはオブジェクトが定義されたとき、つまりメモリ上に実体が確保さ
   れたときに動作
します。そしてコンストラクタはクラスの外部から実行される関数
   なので、publicメンバで指定しなければなりません。
   
   
 仮引数付きコンストラクタ
 
  ・現在のCounterクラスは、オブジェクト定義時にカウンタ値を常に0で初期化します。
   しかし、定義時にある値をセットしたい場合もあるでしょう。このような場合、コ
   ンストラクタに仮引数を持たせることで初期値を与えることができます。
   
   [List3.仮引数付きコンストラクタで初期化したクラスの例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class Counter {
      private:
        int mi_count;      //カウンタ値
      public:
        Counter(int i_init);  //コンストラクタ
        void Init();      //初期化
        void CountUp();     //カウントUp
        void CountDown();    //カウントDown
        int GetCount();     //カウンタ値取得
    };
    
    Counter::Counter(int i_init) { //コンストラクタ
      mi_count = i_init;
      return;
    }
    
       :  省略
       :  Init(), CountUp(), CountDown(), GetCount()
       :  はList1と同様
    
    int main()
    {
      Counter counter_a(2);  //オブジェクト定義
                  //コンストラクタでカウンタ初期化
       :  省略
       :  List1と同様
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     4
   └───────────────────────────────────┘
  
  ・List3では初期値を与える書式として
     Counter counter_a(2);
   と記述していますが、これは
     Counter counter_a = Counter(2);
   の省略記法です。更に仮引数が1つの場合に限られますが
     Counter counter_a = 2;
   記述も可能です。(*2)
  

 コンストラクタのオーバーロードとデフォルト引数

  ・コンストラクタにはオーバーロード及びデフォルト引数を適用することができます。
   例えばList3のCounterは常に初期値を与えねばなりませんが「初期値省略の際は0で
   初期化」
にしたいと仮定します。
  
  ・この機能を実現するために、コンストラクタへオーバーロードやデフォルト引数を
   適用することができます。今回の例ではデフォルト引数が適当でしょう。

   [List4.コンストラクタへのデフォルト引数適用]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class Counter {
      private:
        int mi_count;      //カウンタ値
      public:
        Counter(int i_init=0); //コンストラクタ(デフォルト引数)
        void Init();      //初期化
        void CountUp();     //カウントUp
        void CountDown();    //カウントDown
        int GetCount();     //カウンタ値取得
    };
    
    Counter::Counter(int i_init) { //コンストラクタ
      mi_count = i_init;
      return;
    }
    
       :  省略
       :  Init(), CountUp(), CountDown(), GetCount()
       :  はList1と同様
    
    int main()
    {
      Counter counter_a(2); //初期値付きオブジェクト定義
      Counter counter_b;  //初期値省略
      
      counter_a.CountUp();  counter_b.CountUp();
      counter_a.CountUp();  counter_b.CountUp();
      counter_a.CountDown(); counter_b.CountDown();
      counter_a.CountUp();  counter_b.CountUp();
      
      cout << counter_a.GetCount() << endl;
      cout << counter_b.GetCount() << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     4
     2
   └───────────────────────────────────┘
  
  ・今回の例への適用では多少冗長になりますが、同じ機能をコンストラクタのオーバ
   ーロードで実現してみます。


   [List5.コンストラクタのオーバーロード]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class Counter {
      private:
        int mi_count;      //カウンタ値
      public:
        Counter();       //コンストラクタ(引数無し)
        Counter(int i_init);  //コンストラクタ(引数有り)
        void Init();      //初期化
        void CountUp();     //カウントUp
        void CountDown();    //カウントDown
        int GetCount();     //カウンタ値取得
    };
    
    Counter::Counter() { //コンストラクタ(引数無し)
      mi_count = 0;
      return;
    }

    Counter::Counter(int i_init) { //コンストラクタ(引数有り)
      mi_count = i_init;
      return;
    }
    
       :  省略
       :  Init(), CountUp(), CountDown(), GetCount()
       :  はList1と同様
    
    int main()
    {
      Counter counter_a(2); //初期値付きオブジェクト定義
      Counter counter_b;  //初期値省略
      
       :  省略
       :  List4と同様
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     4
     2
   └───────────────────────────────────┘


 デストラクタ
 
  ・今度は1つのオブジェクトで複数のカウンタ値を持つクラスを作成してみます。クラ
   ス名は MCounter とします。カウンタの個数と初期値を受けて動的に変数を確保す
   る方針とします。
  
  ・動的にメモリを確保するということは、当然解放も行わなければなりません。メモ
   リの解放はオブジェクトが破棄される際に実行したいものです。このようなオブジ
   ェクト破棄時に実行したい処理
デストラクタ(destructor)関数に記述することが
   できます。
  
  ・デストラクタの関数名はクラス名に~(チルダ)を付けたものとなります。デストラク
   タもコンストラクタと同様に戻値がありません。そして仮引数の指定もできません。
   
   [List6.デストラクタ使用例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class MCounter {
      private:
        int* mip_count;           //カウンタ値ポインタ
        int mi_counter_num;         //カウンタ数
      public:
        MCounter(int i_cnum, int i_init=0); //コンストラクタ
        ~MCounter();            //デストラクタ
        void Init(int i_index);       //初期化
        void CountUp(int i_index);     //カウントUp
        void CountDown(int i_index);    //カウントDown
        int GetCount(int i_index);     //カウンタ値取得
    };
    
    MCounter::MCounter(int i_cnum, int i_init) { //コンストラクタ
      mip_count = new int [i_cnum];
      mi_counter_num = i_cnum;
      for (int i=0; i<mi_counter_num; i++){
        *(mip_count+i) = i_init;
      }
      return;
    }

    MCounter::~MCounter() { //デストラクタ
      delete[] mip_count;
      return;
    }

    void MCounter::Init(int i_index){
      if (i_index < mi_counter_num) *(mip_count+i_index) = 0;
      return;
    }

    void MCounter::CountUp(int i_index){
      if (i_index < mi_counter_num) (*(mip_count+i_index))++;
      return;
    }
    
    void MCounter::CountDown(int i_index){
      if (i_index < mi_counter_num) (*(mip_count+i_index))--;
      return;
    }
    
    int MCounter::GetCount(int i_index){
      if (i_index < mi_counter_num) return *(mip_count+i_index);
      else return 0;
    }

    int main()
    {
      MCounter counter(2,1); //カウンタ2個で初期値1
      
      counter.CountUp(0); counter.CountUp(1);
      counter.CountUp(0); counter.CountUp(1);
      counter.CountDown(0); counter.CountDown(1);
      counter.CountUp(0); counter.CountUp(1);
      
      cout << counter.GetCount(0) << endl;
      cout << counter.GetCount(1) << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     3
     3
   └───────────────────────────────────┘


オブジェクトの配列

 オブジェクトの配列と初期化
 
  ・クラスのオブジェクトも配列にすることができます。配列の添字指定については従
   来の配列と全く同じ考え方です。配列で確保したオブジェクトのメンバには、オブ
   ジェクトに添字を付けてアクセスします。
     Counter counter[2];
     counter[0].CountUp(); counter[1].CountUp();
  
  ・List5の初期値付きCounterクラスに対して配列を定義する場合、各配列要素に初期
   値を与えることができます。

     Counter counter[2] = {1, 2};
  
  ・List6のように複数の仮引数を使用するオブジェクトの場合、以下のような記述で初
   期値を与えることができます。
     MCounter counter[2] = {MCounter(2,2), MCounter(3,1)};
                // counter[0]   counter[1]


オブジェクトへのポインタと参照

 ポインタを介したアクセス
 
  ・オブジェクトにはポインタを使用してアクセスすることができます。オブジェクト
   のアドレスを受け取るには構造体の場合と同様に&演算子を使用します。
     Counter counter;
     Counter* p_counter;
     p_counter = &counter;
  
  ・ポインタを介してメンバへアクセスするのも構造体と同様にアロー演算子を使用し
   ます。
     p_counter->CountUp();
  
  ・オブジェクトへのポインタには特別な用途があります。それについては継承と仮想
   関数(2.3章)で説明します。


 参照を介したアクセス
 
  ・オブジェクトにも参照を適用することができます。参照の仕様について特に基本型
   変数と変わる点は何もありません。
  
  ・オブジェクトへの参照は、この後説明するコピーコンストラクタや演算子オーバー
   ロード(2.2章)で多用することになります。


インライン関数

 インライン関数の意味
 
  ・C++にはインライン関数(inline function)と呼ばれる特別な関数があります。イン
   ライン関数は、通常の関数のように呼び出されるのではなく、コードとして展開さ
   れ、プログラムに埋め込まれます。
よって小さい関数を高速に実行するケースに適
   していると言えます。以下のように宣言します。
     inline int SubFunc() {・・・}
  
  ・inline関数はマクロに似ていますが、引数や型の整合性がチェックされるという点
   で大きく異なります。
  
  ・使用する制御構造によってはインライン展開できない場合があります。その判断基
   準は処理系及びコンパイラに任されているので、inline宣言しても必ず展開される
   とは限りません。

  
  ・コンパイラによっては、インライン関数内で使用できない制御構造(例えばforルー
   プ等)を見つけるとコンパイルエラーとして処理する場合もあります。
 
 
 クラス内部のインライン関数定義
 
  ・今までの例では、メンバ関数をスコープ解決演算子によりクラス宣言の外側で定義
   していましたが、実はクラス宣言の内部でもメンバ関数を定義できます。そのとき
   メンバ関数はinline関数として扱われます。

  
   [List7.メンバ関数をinline関数で定義(List4の書き換え)]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class Counter {
      private:
        int mi_count;      //カウンタ値
      public:
        Counter(int i_init=0) { //コンストラクタ(デフォルト引数)
          mi_count = i_init;
          return;
        }
        void Init() {      //初期化
          mi_count = 0;
          return;
        }
        void CountUp() {    //カウントUp
          mi_count++;
          return;
        }
        void CountDown() {   //カウントDown
          mi_count--;
          return;
        }
        int GetCount() {    //カウンタ値取得
          return mi_count;
        }
    };
    
    int main()
    {
      Counter counter_a(2); //初期値付きオブジェクト定義
      Counter counter_b;  //初期値省略
      
      counter_a.CountUp();  counter_b.CountUp();
      counter_a.CountUp();  counter_b.CountUp();
      counter_a.CountDown(); counter_b.CountDown();
      counter_a.CountUp();  counter_b.CountUp();
      
      cout << counter_a.GetCount() << endl;
      cout << counter_b.GetCount() << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     4
     2
   └───────────────────────────────────┘


コピーコンストラクタ

 オブジェクトをコピーする
 
  ・オブジェクトの型が同じであれば、代入(=)演算子によりオブジェクトをコピーする
   ことができます。

     Counter counter_a, counter_b;
      :
     counter_b = counter_a;
 
  ・このときのコピーは構造体の場合と同じで、bit単位で完全なコピーが行われます。
   例えばオブジェクトのメンバにポインタが含まれる際、その参照アドレスもコピーさ
   れます。
  
  ・コンストラクタを持つ場合ですが、代入演算子によるコピーの場合、コンストラク
   タは動作しません。
List8に動作例を示します。

   [List8.コンストラクタを持つオブジェクトの代入演算子コピー]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class DummyCounter {
      private:
        int mi_count;      //カウンタ値
      public:
        DummyCounter(int i_init=0) { //
          mi_count = i_init;
          cout << "コンストラクタ" << endl;
          return;
        }
        ~DummyCounter(){
          cout << "デストラクタ" << endl;
        }
        void Init() {      //初期化
          mi_count = 0;
          return;
        }
        int GetCount() {
          return mi_count;
        }
    };
    
    int main()
    {
      cout << "counter_a定義 : ";
      DummyCounter counter_a(2);
      cout << "counter_b定義 : ";
      DummyCounter counter_b;
      
      cout << "counter_b = counter_a " << endl;
      counter_b = counter_a;
      
      cout << counter_a.GetCount() << endl;
      cout << counter_b.GetCount() << endl;
      
      return 0;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     counter_a定義 : コンストラクタ
     counter_b定義 : コンストラクタ
     counter_b = counter_a
     2
     2
     デストラクタ
     デストラクタ
   └───────────────────────────────────┘


 関数にオブジェクトを渡す
 
  ・関数の引数としてオブジェクトを渡すと、構造体と同様に値渡しとなります。仮引
   数のオブジェクトにメンバデータがコピーされるわけですが、このときもコンスト
   ラクタは動作しません
。しかしローカルオブジェクトが破棄される際、デストラク
   タは動作します。

   
   [List9.関数にオブジェクトを渡す]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class DummyCounter {
      :
      : 省略
      : List7と同様
      :
    };
    
    void SubFunc(DummyCounter counter);
    
    int main()
    {
      cout << "counter_a定義 : ";
      DummyCounter counter_a(2);
      
      cout << "関数へ代入 : " << endl;
      SubFunc(counter_a);
      
      cout << "main : " << counter_a.GetCount() << endl;
      
      return 0;
    }

    void SubFunc(DummyCounter counter)
    {
      cout << "SubFunc : " << counter.GetCount() << endl;
      return;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     counter_a定義 : コンストラクタ
     関数へ代入 :
     SubFunc : 2
     デストラクタ
     main : 2
     デストラクタ
   └───────────────────────────────────┘


 関数からオブジェクトを返す
 
  ・関数からオブジェクトを返す場合を考えてみます。List10ではSubFunc()関数内で
   ーカルなオブジェクトを作成し、そのデータを戻り値として返しています。

   
   [List10.関数からオブジェクトを返す]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class DummyCounter {
      :
      : 省略
      : List7と同様
      :
    };
    
    DummyCounter SubFunc();
    
    int main()
    {
      cout << "counter_a定義 : ";
      DummyCounter counter_a(2);
      
      cout << "関数から代入 : ";
      counter_a = SubFunc();
      
      cout << "main : " << counter_a.GetCount() << endl;
      
      return 0;
    }

    DummyCounter SubFunc()
    {
      DummyCounter counter(3);
      cout << "SubFunc : " << counter.GetCount() << endl;
      return counter;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     counter_a定義 : コンストラクタ
     関数から代入 : コンストラクタ
     SubFunc : 3
     デストラクタ ← 暗黙のコピーオブジェクト破棄時のデストラクタ
     デストラクタ ← SubFuncローカルオブジェクト破棄時のデストラクタ
     main : 3
     デストラクタ
   └───────────────────────────────────┘
  
  ・List10の結果からわかるようにデストラクタは3回動いています。実はオブジェクト
   を戻す際は内部的にオブジェクトのコピーが作成され、それが戻り値としてコピー
   されています。
コピー終了後はオブジェクトが破棄されるのでデストラクタが動作
   します。
  
  ・2回目のデストラクタは、SubFunc()関数内のローカルオブジェクト破棄時に実行さ
   れるもので、3回目のデストラクタはmain()関数内のローカルオブジェクト破棄時に
   実行されています。



 コピーコンストラクタ
 
  ・さて、今まで見てきたように、代入演算子又は関数間のやり取りでオブジェクトの
   コピーが行われた場合、コンストラクタは動作しません。
一方オブジェクト破棄時
   にデストラクタは動作しています。
  
  ・例えば、List6で作成したMCounterクラスのように動的な変数の生成と解放を行って
   いるクラスの場合
どうなるでしょうか。デストラクタで指定する内部のメモリ領域
   ポインタ値もコピーされているので、同じメモリ領域を複数回に渡って解放してし
   まう
ことになります。この動きは絶対にまずいものです。
  
  ・つまりMCounterのようなクラスでは、代入演算子や関数間でのやり取りで生じる
   黙のコピーに対応するコンストラクタが必要
です。特に関数間のオブジェクトやり
   取り時に動作するコンストラクタ
コピーコンストラクタ(copy constructor)
   呼びます。
  
  ・MCounterの場合、コピーコンストラクタの目的は、コピーで生成されたオブジェク
   ト用の変数メモリ領域を別途確保し、デストラクタが動作しても問題ないようにす
   る
ことです。
  
  ・MCounterクラスを例にすると、コピーコンストラクタは以下の書式で定義します。
     MCounter::MCounter(const MCounter& r_counter){・・・}
  
  ・つまり、コピーコンストラクタの実体は引数に同型オブジェクトが指定された場合
   のオーバーロードコンストラクタなのです。コピー元のオブジェクトを参照で受け
   ており、この状態でコピー元から情報を受け取りつつ生成側オブジェクトの条件を
   整えていく
わけです。List11に作成例を示します。

   [List11.List6のMCounterへコピーコンストラクタを追加した例]
   ┌───────────────────────────────────┐
    #include <iostream>
    using namespace std;
    
    class MCounter {
      private:
        int* mip_count;           //カウンタ値ポインタ
        int mi_counter_num;         //カウンタ数
      public:
        MCounter(int i_cnum, int i_init=0); //コンストラクタ
        MCounter(const MCounter& r_counter); //コピーコンストラクタ
        ~MCounter();             //デストラクタ
        void Init(int i_index);       //初期化
        void CountUp(int i_index);      //カウントUp
        void CountDown(int i_index);     //カウントDown
        int GetCount(int i_index);      //カウンタ値取得
    };
    
    MCounter::MCounter(int i_cnum, int i_init) { //コンストラクタ
      mip_count = new int [i_cnum];
      mi_counter_num = i_cnum;
      for (int i=0; i<mi_counter_num; i++){
        *(mip_count+i) = i_init;
      }
      cout << "コンストラクタ" << endl;
      return;
    }
    
    MCounter::MCounter(const MCounter& r_counter){ //コピーコンストラクタ
      mip_count = new int [r_counter.mi_counter_num];
      mi_counter_num = r_counter.mi_counter_num;
      
      for (int i=0; i<mi_counter_num; i++){ //初期値コピー
        *(mip_count+i) = *(r_counter.mip_count+i);
      }
      cout << "コピーコンストラクタ" << endl;
      return;
    }

    MCounter::~MCounter() { //デストラクタ
      delete[] mip_count;
      cout << "デストラクタ" << endl;
      return;
    }

       :  省略
       :  Init(), CountUp(), CountDown(), GetCount()
       :  はList6と同様

    void SubFunc(MCounter counter); //プロトタイプ宣言

    int main()
    {
      MCounter counter(2,1); //カウンタ2個で初期値1
      
      SubFunc(counter); //関数に渡す
      
      cout << "main : " << counter.GetCount(0) << endl;
      cout << "main : " << counter.GetCount(1) << endl;
      
      return 0;
    }

    void SubFunc(MCounter counter)
    {
      counter.CountUp(0); counter.CountUp(1);

      cout << "sub : " << counter.GetCount(0) << endl;
      cout << "sub : " << counter.GetCount(1) << endl;
      
      return;
    }
   └───────────────────────────────────┘
   ┌───────────────────────────────────┐
    [出力結果]
     コンストラクタ
     コピーコンストラクタ
     sub : 2
     sub : 2
     デストラクタ
     main : 1
     main : 1
     デストラクタ
   └───────────────────────────────────┘

  ・コピーコンストラクタを使用すれば、関数間でオブジェクトをやり取りする際に生
   じる暗黙のコピーに対応することができます。しかし「=」代入演算子の場合、更
   に次回説明予定の「演算子オーバーロード」を使う必要があります。



(*1)オブジェクトが自分自身であることを明記する場合、thisポインタを使用します。
    void Counter::Init() {
      this->mi_count = 0; //thisポインタを明記
      return;
    }

(*2)explicitキーワードが付加されたコンストラクタでは
    Counter counter_a = 2;
  の記述が禁止されます。

[Revision Table]
 |Revision |Date    |Comments
 |----------|-----------|-----------------------------------------------------
 |1.00   |2002-07-07 |初版
 |1.01   |2002-07-14 |リンク追加+語句修正
 |1.02   |2002-07-22 |語句修正
[END]
Copyright(C) 2002 Altmo