3.11 一時オブジェクト(1)   (1999.09.12 初版)
 効率を検討する2つ目のテーマとして、「一時オブジェクト」について考察します。
ここでいう「一時オブジェクト」が何であるかはすぐに明らかにしますが、これは言語
というよりも処理系に関する話題です。

3.11.1 コンパイラが見えないところで行うこと

 C++コンパイラは、プログラマの見えないところで様々なことを行っています。例え
ば、クラスにコンストラクタやデストラクタが宣言されていないとき、コンパイラはデ
フォルトコンストラクタとデストラクタを作成します。コピーコンストラクタや代入演
算子がないときもコンパイラはそれらを作成します。
 コンパイラが自動的に作成するデフォルトコンストラクタとデストラクタは何もしな
い空のコードで、これらは単にオブジェクトが生成・消滅できるようにするために作ら
れます。また、コピーコンストラクタや代入演算子が行うのは浅いコピー(データメン
バごとの値コピー)です。
 ここでは、コンパイラが暗黙に作成する「一時オブジェクト」について考えます。

3.11.2 「一時オブジェクト」

 「一時オブジェクト」とは、コンパイラが作業のために必要に応じて一時的に作成す
る、プログラムコードに存在しないオブジェクトのことです。この様なオブジェクトは、
関数呼び出しでオブジェクトを引数に渡すときや、関数がオブジェクトを値渡しで返却
するときに作成されます。

 具体例を簡単なクラスを使って見てみましょう。次のクラス

class X{

public:
    explicit X(int = 0);  // コンストラクタ
    X(const X& a);         // コピー・コンストラクタ
    ~X();                   // デストラクタ

private:
    int data;
};

について、オブジェクトを値渡しで受け取り、値渡しで返す関数

X func(X b)
{
    return b;
}

を呼び出します。

    X a(1);
   func(a);

このとき、オブジェクトがどのように生成・消滅するかをみます。
// lst03_25.cpp
// 暗黙に一時オブジェクトを作成するサンプル・プログラム

#include <iostream.h>

// 簡単なクラス
class X{

public:
    explicit X(int i = 0) : data(i)
    {
        cout << "コンストラクタが呼ばれました。" << endl;
    }
    X(const X& a) : data(a.data)
    {
        cout << "コピー・コンストラクタが呼ばれました。" << endl; 
    }
    ~X()
    {
        cout << "デストラクタが呼ばれました。" << endl;
    }

private:
    int data;
};

// オブジェクトを(値渡しで)受け取り、(値渡しで)返す関数
X func(X b)
{
   //... 普通はここで何らかの操作を行う
    return b;
}

int main()
{
    X a(1);

    func(a);

    return 0;
}
[実行結果]       ソースファイル ( lst03_25.cpp )

コンストラクタが呼ばれました。           
コピー・コンストラクタが呼ばれました。   
コピー・コンストラクタが呼ばれました。   
デストラクタが呼ばれました。             
デストラクタが呼ばれました。             
デストラクタが呼ばれました。             
 結果は、3つのコンストラクタが呼ばれ、3つのデストラクタが呼ばれています。す
なわち、全部で3つのオブジェクトが作られたことを意味します。ところが、プログラ
ムコードに書かれているオブジェクトは2つです。main()関数内には1つのオブジェク
トaがあり、関数 func() 内に引数で渡されたオブジェクトを受け取るローカルオブジ
ェクトbがあります。この2つが、コードにあるオブジェクトです。

 実行結果の最初のコンストラクタと最後のデストラクタは、オブジェクトaに対する
もの。実行結果の2行目は、オブジェクトaを関数 func() に渡すときに引数で受け取
るときに作成されるローカルオブジェクトbに対するコピー・コンストラクタです。従
って実行結果の3行目は、実際のプログラムコードにないオブジェクトの作成を意味し、
これがコンパイラが暗黙に作成した「一時オブジェクト」です。
 この「一時オブジェクト」は、関数 func() がオブジェクトを(値渡しで)返すとき
に返却を成功させるためにオブジェクトの値を一時的に記憶するために作られます。

 「一時オブジェクト」はこの他に、関数呼び出しを成功する目的で作られることがあ
ります。これは既に、§3.5.5 明示的コンストラクタで見たように、関数呼び出しで引
数が関数の引数型と一致しないときに、コンパイラは「一時オブジェクト」を作成(暗
黙の型変換)して関数の引数に渡します。
§3.5.5 明示的コンストラクタで示した例で、関数の引数の型が const参照型である
のにコンストラクタが呼ばれたことを思い出しましょう。

 大きなオブジェクトを扱うプログラムでは、「一時オブジェクト」の作成によるコン
ストラクタとデストラクタの呼び出しのコストは実行効率に影響します。ここでの目的
は、「一時オブジェクト」の振る舞いを理解することです。

3.11.3 演算子+

 上の例でみたように、関数がオブジェクトを値で返却するときに「一時オブジェクト」
が作成されます。行列やベクトルのクラスでは、演算子+、−、*などが該当します。
ここでは、行列クラスの演算子+を例にして詳しく検討します。まず演算子+の定義を
もう一度確認しておきましょう。コーディングには、演算子+=と関連させて行いまし
たので演算子+=の定義も一緒に示します。
// 多重定義された+演算子
// 2つの行列の和を求め、値渡しで返す
const Matrix operator+(const Matrix &left, const Matrix &right)
{
    Matrix m = left;    //一時的なローカルオブジェクトを作る
    return m += right; // 演算子 += を使う
}

// 多重定義された +=演算子
Matrix &Matrix::operator+=(const Matrix &right)
{
    if((Row != right.Row) || (Col != right.Col)){//サイズのチェック     
        cout << "エラー:行列のサイズが一致しません\n";
        abort();
    }
    for (int i = 0; i < Row; i++)
        ptr[i] += right.ptr[i];

    return *this;
}
 「§3.7.5 演算子+を演算子+=と関連させて実装する」でみたように、演算子+=
は演算子+よりも、実装が簡単で実行効率も良くなります。理由は、演算子+=つまり、
Matrix::operator+=() ではオブジェクト自身を直接変更する(従って返却値型は参
照型)のに対して、演算子+つまり operator+() は足し算結果を求めるための一時
的なオブジェクト

    Matrix m = left;    //一時的なローカルオブジェクトを作る
    return m += right; // 演算子 += を使う

を必要とするというものでした。

 この上更に演算子+は、関数がオブジェクトを返すときに「一時オブジェクト」を作
成します。つまり、演算子+は2つの余分なオブジェクトを作成します。
 ここで、一時的なローカルオブジェクトと「一時オブジェクト」の違いに注意してく
ださい。一時的なローカルオブジェクトmは、結果を求めるためにコード上必要なオブ
ジェクトです。これは、プログラマが必要とするものです。
 一方、「一時オブジェクト」は、この例では関数の返却を実現するためにコンパイラ
が必要とするために暗黙に作成されたオブジェクトです。これを区別するために、カッ
コ「」を付けています。

 以上のことを頭に入れておきましょう。つまり、演算子+は2つの余分なオブジェク
トを作成します。この様子を実際に見ることにします。行列の足し算を全く等価な2つ
の方法で求めます。

c = a + b; // 演算子+を使う

と、

c = a;
c += b;   // 演算子+=を使う

とでは全く同じ結果が得られますが、呼び出される演算子関数が違います。次のプロ
グラムは(最初の頃にやったように)、どのコンストラクタとデストラクタが呼び出
されたかが分かるように、出力メッセージのコードを入れてあります。
// lst03_26.cpp
// コンパイラが暗黙に「一時オブジェクト」を作成する
// Matrix17.cppと一緒にコンパイルする

#include <iostream.h>
#include "matrix17.h" // インターフェース部はこちら

int main()
{
    cout << "3つのオブジェクト a, b, cをインスタンス化します。" << endl; 
    Matrix a(5,5), b(5,5), c(5,5);
    for(int i = 1; i < 5; i++)
        for(int j = 1; j < 5; j++){
            a[i][j] = 2.0*i + 0.2*j;
            b[i][j] = 3.0*i - 0.3*j;
        }

    cout << endl << "代入: c = a + b;" << endl;
    c  = a + b;
    cout << "代入後:" << endl;

    cout << endl << "代入: c = a; c += b;" << endl;
    c = a;
    c += b;
    cout << "代入後:" << endl << endl;

    return 0;
}
[実行結果]       ソースファイル ( lst03_26.cpp )
3つのオブジェクト a, b, cをインスタンス化します。
コンストラクタが呼ばれました(5 行 5 列の行列が作成されました)。
コンストラクタが呼ばれました(5 行 5 列の行列が作成されました)。
コンストラクタが呼ばれました(5 行 5 列の行列が作成されました)。

代入: c = a + b;
コピーコンストラクタが呼ばれました(5 行 5 列の行列が作成されました)。 
コピーコンストラクタが呼ばれました(5 行 5 列の行列が作成されました)。 
デストラクタが呼ばれました(5 行 5 列の行列を解放しました)。
デストラクタが呼ばれました(5 行 5 列の行列を解放しました)。
代入後:

代入: c = a; c += b;
代入後:

デストラクタが呼ばれました(5 行 5 列の行列を解放しました)。
デストラクタが呼ばれました(5 行 5 列の行列を解放しました)。
デストラクタが呼ばれました(5 行 5 列の行列を解放しました)。
 演算子+が呼び出されたときに、2つのコピー・コンストラクタが呼ばれます。はじ
めの方は、演算子関数 operator+() 内のローカルオブジェクト

    Matrix m = left;    //一時的なローカルオブジェクトを作る

の作成によるものです。そして2番目は、関数がオブジェクトを(値渡しで)返却する
ときにコンパイラが暗黙に作成した「一時オブジェクト」です。2つのデストラクタが
それぞれどちらに対応するかは、後で明らかにします。一方、演算子+=の呼び出しで
は、この様な一時オブジェクトは必要としません。

3.11.4 なぜコンパイラは「一時オブジェクト」を作るか

 なぜコンパイラは関数がオブジェクトを値で返すときに「一時オブジェクト」を必要
とするのか、次のページで詳しく調べます。ここでは、その理由と仕組みを簡単に述べ
ておきます。

c = a + b;

では、演算子+と代入演算子=が実行されます。

c.operator=( operator+(a,b) );

まず、足し算「a + b」のために演算子関数 operator+() が呼び出されます。この
関数は次のように定義されていて、

const Matrix operator+(const Matrix &left, const Matrix &right)
{
    Matrix m = left;    //一時的なローカルオブジェクトを作る
    return m += right; // 値を返す
}

足し算結果の一時的なローカルオブジェクトmを値で返します。このとき、オブジェク
トmは return文が実行されたときに消滅します。
 ところが、関数が値を返しそれを代入演算子に渡すためには、その値を保持するオブ
ジェクトが必要です。そのために、mが消滅する前に(つまりreturn文が実行される
前に)この値を記憶する「一時オブジェクト」をコンパイラが作成してmの値のコピー
を作ります。そして、ローカルオブジェクトmが消滅します。
 次にオブジェクトcの代入演算子に、この「一時オブジェクト」が渡され代入が成功
したとき、すなわち次の文の実行

c = a + b;

の完了(文の最後の;)で、この「一時オブジェクト」が消滅します。

 つまり上の実行結果の「代入:c = a + b;」で、2つのデストラクタのうち最初の方
はローカルオブジェクトmに対するデストラクタで、2番目は「一時オブジェクト」の
ものです。

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

Copyright(c) Yamada, K