3.3 コンストラクタとデストラクタ   (1999/07/17 初版)

 コンストラクタは、オブジェクトが生成されるとき自動的に呼び出される特殊なメン 
バ関数でした。コンストラクタの役割はデータメンバを正しく初期設定することです。
同様にオブジェクトが消滅するときに、後始末をするために自動的に呼び出される特殊
なメンバ関数にデストラクタがあります。 
 わたしたちが取り組んでる行列やベクトルではメモリを動的に割り当てる作業を行い 
ます。クラスを使うと、これらメモリ割り当てと解放をコンストラクタとデストラクタ 
に行わせることができます。 

 尚、以下の設計では例外処理は行っていません。またベクトル・クラスは演習問題と 
して残します。これら行列とベクトルのクラスは後に統合されます。 

3.3.1 内部データのレイアウト

 行列クラスの内部データのレイアウトについて検討します。行列を表現するデータ構 
造つまりデータメンバは、行数と列数それに行列要素のメモリ領域を指すポインタの3 
つです。これをクラスで表現しましょう。次のようなMatrixクラスを考えます。 

class Matrix{ 

public: 
     // まだ設計中 
     // ここにはコンストラクタとデストラクタが置かれる 
     // .... 
    void setSize(int, int);     // 行列のサイズを再設定する 
    int getRow() { return Row;} // 行数を取得 
    int getCol() { return Col;} // 列数を取得 

private: 
    double **ptr;// 行列要素へのポインタ 
    int Row; // 行の数 
    int Col; // 列の数 
}; 

これは、以前のテーマ「行列ライブラリの設計(1)」で作成したC構造体を使った行 
列のデータ表現(matrix11.h)のクラス版です。 

 データメンバはすべて非公開メンバにするのが原則です。そして隠蔽されたこれらの 
データを操作するために公開メンバ関数を用意します。public部にある3つのメンバ 
関数は行列のサイズを再設定し、現在の行数や列数を取得します。データメンバを操作 
するのに必要十分な公開メンバ関数の完全なセットを用意すれば、データの内部実装は 
クラスの中に隠蔽することができます。ただし、今のところ行列要素にアクセスするた 
めの方法を用意していませんが、後で洗練されたやり方を示します。 

 Matrixクラスは必要に応じて行列のサイズ(行と列のサイズ)を変更することができ 
ます。このクラスのオブジェクトのデータメンバ ptrは、始め Row × Colのサイズの 
メモリ領域を指しています。行列のサイズを変更するには、ptr が指すメモリを解放し、 
新しいサイズで割り当てたメモリ領域を指すように ptrを変更します。これらの操作は 
すべて公開メンバ関数が行います。 
 この様な実装の詳細は、クラスを利用するプログラムからは隠されています。従って、 
クラスのユーザは動的メモリ割り当てについて全く意識することなくオブジェクトを扱 
うことができるようになります。 

3.3.2 インターフェースとメンバ関数の定義を分離する

 クラスを利用するプログラムからオブジェクトを介して見えるのは公開メンバ関数だ 
けです。オブジェクトの属性(データメンバ)はこれらの公開メンバ関数を使って操作 
します。つまり公開メンバ関数はオブジェクトの内部を操作するためのインターフェー 
スの役割をします。そのためこれらは、公開インターフェースと呼ばれます。 

 メンバ関数の定義はクラス定義の中で行うこともできますが、むしろクラス定義の外 
で行う方が望ましいとされています。これは“インターフェースと内部実装を分離する” 
という、ソフトウェア工学の基本原理の1つです。つまりクラスのインターフェースに 
関する重要な情報はクラス定義ですので、これをヘッダファイルに入れます。そして内 
部実装であるメンバ関数の定義は別の(通常は同一名の)ソースコード・ファイルに入 
れます。この様にすることでインターフェースがスッキリします。更に、クラスの更新 
(バージョンアップ)が容易になります。クラスの内部実装が変更されても、インタフ 
ェースが変わらない限り、クラスを利用するプログラムを変更する必要がないからです。

3.3.3 コンストラクタの多重定義

 コンストラクタを実装します。Matrixクラスのオブジェクトを生成するには、次の3 
つのタイプが考えられます。 

Matrix a; // 既定値で初期化 

Matrix a(5,6); // 5行6列の行列を生成 

Matrix a = b; // 既に存在するオブジェクトbの値と同じものを生成 

これら3つの初期化に応じたコンストラクタを多重定義します。1つ目は、引数のない 
デフォルトコンストラクタで次のようなプロトタイプで与えられるものです。 

Matrix::Matrix(); 

2つ目は引数に行数と列数の2つの引数を与えて、その次元数を持つオブジェクトを生 
成するコンストラクタで、 

Matrix::Matrix(int, int); 

というものです。実はこれら2つは1つのコンストラクタで表現することができます。 

Matrix::Matrix(int = 0, int = 0); 

の様にデフォルト引数を用意することで、2つの機能を兼ねることができます。この場 
合、次の宣言 

Matrix a; 

ではメンバ関数の(デフォルト)コンストラクタ「 a.Matrix( 0, 0); 」が自動的に 
呼び出され、また次の宣言では 

Matrix b(10,20); 

コンストラクタ「 b.Matrix( 10, 20); 」が呼び出されます。 

 3つ目は、既存のオブジェクトの値を使ってオブジェクトを生成するものです。これ 
は丁度組み込み型の変数で、次の初期化と同様のことを行えるようにするためです。 

int i = 10; 
int j = i; // iの値で初期化 

このタイプのコンストラクタをコピーコンストラクタと呼び、次のプロトタイプで表さ 
れます。 

Matrix::Matrix(const Matrix &); 

この様なコンストラクタを用意することで、次のような宣言 

Matrix b = a; // aはMatrixクラスのオブジェクト 

では 

b.Matrix(a); 

が呼び出され、オブジェクトbは生成時にaの属性(データメンバ)で初期設定されま 
す。この詳細はすぐ後で扱います。 

 Matrix クラスの定義の最初のバージョンは次のようになります。以前の続きから、 
ヘッダファイル名を matrix12.h とし内部実装のソースファイルを matrix12.cpp と 
します。ヘッダファイルは、クラス定義をプリプロセッサ命令で次のように囲んで2重 
にインクルードされることを防いでいます。 

#ifndef MATRIX12_H 
#define MATRIX12_H 
  ...... 

#endif 
// matrix12.h
// Matrixクラス インターフェイス部
#ifndef MATRIX12_H
#define MATRIX12_H

#include <iostream.h>
#include <stdlib.h>

class Matrix{

public:
    Matrix(int = 0, int = 0);// デフォルトコンストラクタ
    Matrix(const Matrix &);// コピーコンストラクタ
    ~Matrix();			   // デストラクタ
    void setSize(int, int);// 行列のサイズを設定する
    int getRow() const { return Row;}// 行数を取得
    int getCol() const { return Col;}// 列数を取得

private:
    double **ptr;// 要素へのポインタ
    int Row;	// 行の数
    int Col;	// 列の数

    // ユーティリティ関数
    void new_matrix();// 行列の領域を確保する
    void del_matrix();// 領域を開放する
};

#endif
                    ヘッダファイル matrix12.h

3.3.4 メモリの動的作成と解放

 行列要素のメモリ領域を作成・解放する場面はいまのところ3つあります(後でもう 
1つ加わります)。 

(1)オブジェクトの生成(コンストラクタが行う) 
(2)行列のサイズ変更(メンバ関数 Matrix::setSize() が行う) 
(3)オブジェクトの消滅(デストラクタが行う) 

メモリ領域の動的作成と解放は、前回のテーマ「行列ライブラリの設計(1)」で作成 
した非オブジェクト指向の方法と基本は同じです。例えば matrix11.h で定義した関数 

void mat_new(matrix &a, int m, int n);// 行列の動的作成 
void mat_del(matrix &a);               // 行列の解放 

を思い出してください。これらをメンバ関数にしたのが上のクラス定義のなかの 

void new_matrix();// 行列の領域を確保する 
void del_matrix();// 領域を開放する 

です。ここで1つの特徴に気付きます。メンバ関数の方には引数がありません。オブジ 
ェクト指向プログラミングでは渡す引数の個数が減ります。そのため関数呼び出しが簡 
略化されます。これはカプセル化の機能がもつ利点の1つです。 
 またクラス定義での関数の位置に注目しましょう。これらは、非公開メンバ関数です。 
これらの関数は上の3つ場面でそれぞれ公開メンバ関数の中から呼ばれます。クラスの 
ユーザが直接利用する必要はありません。そのためインタフェースに含めないで、非公 
開メンバとしています。この様な関数をユーティリティ関数と呼びます。次に定義を 
示します。 

// 行列の領域を確保する 
void Matrix::new_matrix() 
{ 
    // デフォルト値ではポインタをヌルに設定 
    if(Row == 0 || Col == 0){ 
        Row = 0; 
        Col = 0; 
        ptr = 0; 
        return; 
    } 
    ptr = new double *[Row];     //行の設定(行ベクトルへのポインタ配列)
    for(int i = 0; i < Row; i++) //列の設定(行ベクトル)
        ptr[i] = new double[Col]; 
} 

// 行列の領域を開放する 
void Matrix::del_matrix() 
{ 
    for(int i = 0; i < Row; i++) // 列を解放 
        delete [] ptr[i]; 

    delete [] ptr; //行を解放 
} 

コンストラクタは内部でユーティリティ関数Matrix::new_matrix()を呼び出します。 

// (デフォルト)コンストラクタ 
Matrix::Matrix(int row, int col) 
{ 
    Row = row;    // データメンバを設定 
    Col = col; 
    new_matrix(); // 行列の領域確保 

    for(int i = 0; i < Row; i++) //要素を0に初期設定 
        for(int j = 0; j < Col; j++) 
            ptr[i][j] = 0.0; 
} 

コンストラクタ内で、ユーティリティ関数Matrix::new_matrix()が呼び出される前に 
オブジェクトのデータメンバ Rowと Colが設定されていることに注意しましょう。 

3.3.5 コピーコンストラクタ

 コピーコンストラクタは、同じクラスに属するオブジェクトの値で初期設定するとき 
に呼び出されるコンストラクタです。というクラスの名前があるとき、コピーコンス 
トラクタは::(const &)というプロトタイプになります。これにより、次の 
ようなオブジェクトを生成できます。 

X b = a; // aは既存のクラスのオブジェクト 

コピーコンストラクタは動的メモリ割り当てを行うクラスでは必ず必要になります。 
わたしたちのクラスでは次のように定義されます。 

// コピーコンストラクタ 
Matrix::Matrix(const Matrix &a) 
{ 
    Row = a.Row; // 行数をコピー 
    Col = a.Col; // 列数をコピー 

    new_matrix(); // aと同じサイズの行列領域を確保 

    for(int i = 0; i < Row; i++) // aの要素を代入 
        for(int j = 0; j < Col; j++) 
            ptr[i][j] = a.ptr[i][j]; 
} 

これで、次のような宣言でオブジェクトbを初期設定できます。 

Matrix b = a; // 既存のオブジェクトaの値と同じものを生成 

3.3.6 デストラクタ

 組み込み型の変数はその変数のスコープの終わりで消滅します。例えば関数内で宣言 
された普通の変数は関数ブロックの終わりで消滅します。クラスのオブジェクトも同じ 
です。そのときに自動的に呼ばれるのがデストラクタです。デストラクタを使うこと 
でオブジェクトが消滅する前に後始末をすることができます。コンストラクタが動的メ 
モリを作成した場合は、それを解放するのがデストラクタの役割です。 

 デストラクタはクラス名の前にチルダ(〜)が付いた公開メンバ関数です。上のクラス 
定義の ~Matrix() がデストラクタです。 
 デストラクタは引数を持たず、コンストラクタと同様に戻り値型を持ちません。クラ 
スにはデストラクタを1つだけ設けることができます。デストラクタは多重定義できま 
せん。 

 デストラクタはなぜチルダ(〜)で命名規約されたのでしょう。チルド演算子(〜)は 
ビット補数演算子(ビットNOT演算子)です。つまりデストラクタはコンストラクタの 
補数(反転)という意味なのでしょう。次にデストラクタの定義を示します。 

// デストラクタ 
Matrix::~Matrix() 
{ 
    del_matrix(); 
} 

これは、ユーティリティ関数Matrix::del_matrix() を呼び出すだけです。 


3.3.7 constメンバ関数

 Matrixクラスの定義 (matrix12.h)のなかの行数と列数を取得するメンバ関数で、関 
数宣言の引数リストの後ろのキーワード const に注目してください。 

Matrix::getRow() const { return Row; } 
Matrix::getCol() const { return Col; } 

これは、メンバ関数がオブジェクトを変更しないことを意味します。実際これらの関数 
はデータメンバの値を返すだけで、オブジェクトの内部状態を変更していません。この 
様なメンバ関数を、constメンバ関数定数メンバ関数)といいます。この場合、コ 
ンパイラは関数がオブジェクトを変更しようとする箇所を検出します。 
 またconstメンバ関数をクラスの外で定義するときにも、constを後ろに付加しなけ 
ればなりません。キーワードconstは関数の型の一部です。 

 メンバ関数がオブジェクトの値を変更しないなら、必ずconstメンバ関数にしてオブ 
ジェクトを変更禁止にしておくことが大切です。つまり必要以上の権限を持たせないよ 
うにします。さらにこの様にすることで、クラスの利用者(別のプログラマ)に「オブ 
ジェクトを変更しないメンバ関数」であることを知らせることができます。 

 普通のメンバ関数は呼びだされるとき、現在のオブジェクトを指すthisポインタが暗
黙に渡されます。このときthisポインタの型は、そのクラス名がのときは「*」型
です。 
 一方constメンバ関数の場合、暗黙に渡されるのは「定数オブジェクトへのポインタ」 
になります。つまりthisの型は「const *」型です。従ってオブジェクトは変更不 
可となり、コンパイラはこれを強制します。これがconstメンバ関数の仕組みです。 

3.3.8 メンバ関数の定義部

 以上をまとめたメンバ関数定義部は次のようになります。
// matrix12.cpp
// Matrixクラス メンバー関数定義部
#include "matrix12.h"

// デフォルトコンストラクタ
Matrix::Matrix(int row, int col)
{
    cout <<  "コンストラクタが呼ばれました(";

    Row = row;
    Col = col;
    new_matrix();  // 行列の領域確保

    for(int i = 0; i < Row; i++) //要素を0に初期化
        for(int j = 0; j < Col; j++)
            ptr[i][j] = 0.0;

    cout << Row << " 行 " << Col << " 列の行列が作成されました)。" << endl;
}

// コピーコンストラクタ
Matrix::Matrix(const Matrix &init)
{
    Row = init.Row;
    Col = init.Col;

    new_matrix();	// 行列の領域確保

    for(int i = 0; i < Row; i++)//要素を代入
        for(int j = 0; j < Col; j++)
            ptr[i][j] = init.ptr[i][j];

    cout << "コピーコンストラクタが呼ばれました("
    << Row << " 行 " << Col << " 列の行列が作成されました)。" << endl;
}

// デストラクタ
Matrix::~Matrix()
{
    cout << "デストラクタが呼ばれました("
    << Row << " 行 " << Col << " 列の行列を解放します)。" << endl;

    del_matrix();
}

// 行列のサイズを設定する
void Matrix::setSize(int row, int col)
{
    del_matrix();

    Row = row;
    Col = col;

    new_matrix(); // 行列の領域確保

    for(int i = 0; i < Row; i++)//初期設定
        for(int j = 0; j < Col; j++)
            ptr[i][j] = 0.0;
}

// 行列の領域を確保する
void Matrix::new_matrix()
{
    // デフォルト値ではポインタをヌルに設定
    if(Row == 0 || Col == 0){
        Row = 0;
        Col = 0;
        ptr = 0;
        return;
    }
    ptr = new double *[Row]; //行の設定
    if(ptr == 0){
        cout << "エラー:領域確保に失敗しました。\n";
        abort();
    }
    for(int i = 0; i < Row; i++){  //列の設定
        ptr[i] = new double[Col];
        if(ptr[i] == 0){
            while(--i) delete [] ptr[i];
            delete [] ptr;
            cout << "エラー:領域確保に失敗しました。\n";
            abort();
        }
    }
}

// 領域を開放する
void Matrix::del_matrix()
{
    for(int i = 0; i < Row; i++) // 列を解放
        delete [] ptr[i];

    delete [] ptr; //行を解放
}
          ソースプログラム  matrix12.cpp

 ここで、メモリ領域の動的作成のときに、new演算子が失敗したときヌルポインタを 
返すコンパイラの場合の処理を含めています。現在の標準C++では例外を送出します。 
そのようなコンパイラではこのコードは実行されることはありません。しかしヌルポイ 
ンタを返す場合はこのエラー処理は最低限必要です。 

3.3.9 コンストラクタとデストラクタが呼ばれる順序

 コンストラクタとデストラクタはどのように呼ばれるのかを調べます。次のプログラ 
ムを実行します。この場合、このドライバプログラムlst03_06.cppとMatrix12.cppと 
を一緒にコンパイルします(分割コンパイルのページ参照)。 
// lst03_06.cpp
//Matrixクラスのドライバープログラム
//Matrix12.cppと一緒にコンパイルする
#include <iostream.h>
#include "Matrix12.h"

int main()
{
    cout << "オブジェクトaの生成:" << endl;
    Matrix a;

    cout << endl << "オブジェクトbの生成:" << endl;
    Matrix b(3,4);

    cout << endl << "オブジェクトcの生成:" << endl;
    Matrix c = b;

    cout << endl << "オブジェクトcを変更:";
    c.setSize(8,9);
    cout << c.getRow() <<  "×" << c.getCol()
    << "行列になりました。" << endl << endl;

    return 0;
}
[実行結果]          ソースプログラム  lst03_06.cpp
オブジェクトaの生成: 
コンストラクタが呼ばれました(0 行 0 列の行列が作成されました)。 

オブジェクトbの生成: 
コンストラクタが呼ばれました(3 行 4 列の行列が作成されました)。 

オブジェクトcの生成: 
コピーコンストラクタが呼ばれました(3 行 4 列の行列が作成されました)。 

オブジェクトcを変更:8×9行列になりました。 

デストラクタが呼ばれました(8 行 9 列の行列を解放します)。 
デストラクタが呼ばれました(3 行 4 列の行列を解放します)。 
デストラクタが呼ばれました(0 行 0 列の行列を解放します)。 
このプログラムは3つのオブジェクトを生成します。まずaのデフォルトコンストラク 
タが呼ばれます。次にbのコンストラクタが呼ばれ、最後にcのコピーコンストラクタ 
が呼ばれます。そして、main()関数が終わるときに、これらオブジェクトのデストラク 
タが呼ばれます。 
 実行結果より、コンストラクタが呼ばれた逆の順にデストラクタが呼ばれていること
が分かります。 

 次の例は、オブジェクトが関数内で生成される場合です。
// lst03_07.cpp
//Matrixクラスのドライバープログラム
//Matrix12.cppと一緒にコンパイルする
#include <iostream.h>
#include "Matrix12.h"

void func()
{
    cout << "関数内で行列オブジェクトを生成" << endl;
    Matrix a;
}

int main()
{
    cout << "関数呼び出し前:" << endl;
    func();
    cout << "関数呼び出し後:" << endl;

    return 0;
}
[実行結果]          ソースプログラム  lst03_07.cpp
関数呼び出し前: 
関数内で行列オブジェクトを生成 
コンストラクタが呼ばれました(0 行 0 列の行列が作成されました)。 
デストラクタが呼ばれました(0 行 0 列の行列を解放します)。 
関数呼び出し後:

 関数内で生成されたオブジェクトは、生成時にコンストラクタが呼び出され、関数 
ブロックの終わりでこのオブジェクトは消滅します。そのときにデストラクタが呼ば 
れ後始末(メモリの解放)をします。

3.3.10 new演算子、delete演算子とクラス

 オブジェクトを動的に作成する場合を調べてみます。次のプログラムを見てくださ 
い。 
// lst03_08.cpp
// new演算子、delete演算子とコンストラクタ、デストラクタ
// Matrix12.cppと一緒にコンパイルする
#include <iostream.h>
#include "Matrix12.h"

int main()
{
    cout << "オブジェクトを指すポインタを宣言" << endl;
    Matrix *ptr;

    cout << "Matrixオブジェクトのメモリ領域を動的に作成します。" << endl;
    ptr = new Matrix(3,4);

    cout << endl << "サイズを変更:";
    ptr->setSize(5,6);
    cout << ptr->getRow() <<  "×" << ptr->getCol()
    << "行列になりました。" << endl << endl;


    cout << "メモリ領域を解放します。" << endl;
    delete ptr;
    cout << "メモリ領域を解放後" << endl;

    return 0;
}
[実行結果]          ソースプログラム  lst03_08.cpp
オブジェクトを指すポインタを宣言 
Matrixオブジェクトのメモリ領域を動的に作成します。 
コンストラクタが呼ばれました(3 行 4 列の行列が作成されました)。 

サイズを変更:5×6行列になりました。 

メモリ領域を解放します。 
デストラクタが呼ばれました(5 行 6 列の行列を解放します)。 
メモリ領域を解放後 
このプログラムでは、まずMatirxクラスのオブジェクトを指すポインタを用意します。 

Matrix *ptr; 

この段階では、オブジェクトは何も作られていないことに注意してください。存在す 
るのはオブジェクトのアドレスを変数とするポインタ ptrです。そして 

ptr = new Matrix(3,4); // コンストラクタが呼ばれる 

でコンストラクタが呼ばれオブジェクトが生成されます。new演算子はオブジェクトの 
メモリ割り当てだけでなく初期化を行います。これは完全なオブジェクトの生成です。 
ただしnew演算子がつくりだすのは、いわば「名無しのオブジェクト」です。その代わ 
りそのアドレスを返します。つまり、new演算子はクラスのコンストラクタを呼び出す 
ことができます。同様に、delete演算子はデストラクタを呼び出します。そして(デ 
ータメンバ ptrが指すメモリの)後始末をした後に(オブジェクトの)メモリを解放し 
ます。 

delete ptr; // デストラクタが呼ばれる 

 new演算子とdelete演算子はこの様な機能を持つので、Cの標準ライブラリ関数 
malloc() と free() の組み合わせよりも優れています。これにより、ポインタを 
使って名前無しのオブジェクトを正しく初期化し、それを利用することができます。 

3.3.11 関数への値渡しと参照渡し

 最後に、関数呼び出しでコンストラクタが呼び出される例を示します。関数の引数に 
オブジェクトを渡すときは、値渡しで行われます。このときに関数内にローカル・オブ 
ジェクトが作られます。コピーコンストラクタはそのローカル・オブジェクトを作成す 
るために呼ばれます。コピーコンストラクタの役割は、引数で渡された呼び出し側のオ 
ブジェクトで初期化することです。そして関数の終了で、デストラクタが呼ばれてロー 
カル・オブジェクトは消滅します。 

 次のプログラムは、2つの関数にオブジェクトを渡します。 

void func1(Matrix ); 
void func2(Matrix &); 

1つ目の関数は値渡しで、2つ目は参照渡しです。これらの関数は共にオブジェクトの 
サイズを変更して終了します。 
// lst03_09.cpp
// 関数への値渡しと参照渡し
// Matrix12.cppと一緒にコンパイルする
#include <iostream.h>
#include "Matrix12.h"

void func1(Matrix a)
{
    cout << endl << "[関数 func1()内]" << endl;
    cout << a.getRow() <<  "×" << a.getCol() << "行列です。" << endl;
    cout << "サイズを変更します。" << endl;
    a.setSize(5,6);
    cout << a.getRow() <<  "×" << a.getCol() << "行列です。" << endl;
}

void func2(Matrix &a)
{
    cout << endl << "[関数 func2()内]" << endl;
    cout << a.getRow() <<  "×" << a.getCol() << "行列です。" << endl;
    cout << "サイズを変更します。" << endl;
    a.setSize(7,8);
    cout << a.getRow() <<  "×" << a.getCol() << "行列です。" << endl;
}

int main()
{
    Matrix a(3,4); // オブジェクトを生成

    // 値渡し
    cout << "関数 func1()呼び出し前:" << endl << endl;
    func1(a);
    cout << "関数 func1()呼び出し後:" << endl << endl;

    // 参照渡し
    cout << "関数 func2()呼び出し前:" << endl;
    func2(a);
    cout << "関数 func2()呼び出し後:" << endl << endl;

    return 0;
}
[実行結果]          ソースプログラム  lst03_09.cpp
コンストラクタが呼ばれました(3 行 4 列の行列が作成されました)。 
関数 func1()呼び出し前: 

コピーコンストラクタが呼ばれました(3 行 4 列の行列が作成されました)。 

[関数 func1()内] 
3×4行列です。 
サイズを変更します。 
5×6行列です。 
デストラクタが呼ばれました(5 行 6 列の行列を解放します)。 
関数 func1()呼び出し後: 

関数 func2()呼び出し前: 

[関数 func2()内] 
3×4行列です。 
サイズを変更します。 
7×8行列です。 
関数 func2()呼び出し後: 

デストラクタが呼ばれました(7 行 8 列の行列を解放します)。 
値渡しの関数ではコピーコンストラクタが呼ばれて、呼び出し側のオブジェクトと同じ 
値(3行4列の行列)のオブジェクトが生成されます。関数内でオブジェクトは変更さ 
れ、終了時にデストラクタが呼ばれてこのローカル・オブジェクトは消滅します。この 
とき呼び出し側のオブジェクトは変更を受けません(値呼び出しだから)。 
 一方参照渡しでは、呼び出し側のオブジェクト自体が渡されますから、コンストラク 
タは起動されません。そして関数内でのオブジェクトの変更は、元のオブジェクトその 
ものを変更することを示します。 

 これと同じ事情は、関数がオブジェクトを返すときにも生じます。戻り値型がクラス 
型(つまり値渡し)の関数が戻るときコピーコンストラクタが呼ばれます。関数の戻り 
型で特に注意すべきとことはオブジェクトへの参照を返す関数の場合です。関数内で生 
成したオブジェクトへの参照を関数が返すと重大な実行エラーを引き起こします。 

 これらについては、後のページで再び詳しく検討します。 
  

[演習問題 3.1] ベクトルを表現するクラス Vectorを設計しなさい。 
        回答:インターフェース部( vector01.h ) 
           実装部( vector01.cpp ) 

[演習問題 3.2] 演習問題3.1で作成したクラスをテストするプログラム 
       を書きなさい。 
       回答:( ex03_01.cpp

| 目次 | 前のページ | 次のページ | ページの先頭 |

Copyright(c) 1999 Yamada,K