一時オブジェクトに関する知識不足
上の例とも関係していますが、一時オブジェクトに関する知識不足による不具合も少なからず見かけます。一時オブジェクトの話に入る前に、まずは参照について簡単に触れておきます。以下の処理をご覧ください。
#include <stdio.h> int& getParm() { static int parm = 0; return parm; } int main(void) { printf("parm = %d\n",getParm()); getParm() = 7; // A printf("parm = %d\n",getParm()); return 0; }
parm = 0 parm = 7
コメントAの箇所は、見慣れないソースコードになっています。このような書き方はとても推奨できるものではありませんが、説明のためにあえて書きました。読者の皆様はこの処理を理解できるでしょうか。getParm
関数の戻り値はint
型への参照型で、静的変数parm
への参照となりますから、この処理はparm
へ7を代入する処理となります。この場合、parm
が静的な変数なので関数の外部からその値を参照することができますが、parm
を自動変数にしてしまうとそのようなわけにはいきません。
#include <stdio.h> int& getParm() { int parm = 0; return parm; } int main(void) { int &p = getParm(); printf("parm = %d\n",p); // A }
getParm
関数の戻り値parm
は自動変数ですから、getParm
関数が終了すればその役目を終え、破棄されます。ところがmain
関数でparm
への参照であるp
の値を、コメントAの箇所で参照していますので、これは破棄されたローカル変数への不正アクセスです。通常業務でC++を使用しているプログラマであれば、このような間違いを犯すことはないと信じますが、C++初心者が陥りがちなワナであることには注意しておく必要があります。
ここで、ようやく一時オブジェクトの話になります。次の関数が呼ばれた場合はどのように振舞うのでしょうか。
#include <iostream> #include <string> using namespace std; const string getABC() { return "ABC"; } int main(void) { const string& abc = getABC(); cout << abc << '\n'; }
この場合、getABC
関数の戻り値のデータ型が実際の戻り値と異なっていますが、string
クラスに文字列リテラルをコンストラクタの仮引数として受け取ることができるため、一時オブジェクトが作成され、そのオブジェクトへの参照が変数abc
に格納されます。
以下の例をご覧ください。
#include <stdio> enum{ CAT; DOG; }; CString getKind(int number) { if (number == CAT){ return "cat"; } else { return "dog"; } } int main(void) { printf("%s\n", (const char *)getKind(CAT)); /* A */ const TCHAR *temp = getKind(DOG); printf("%s\n", temp); /* B */ }
コメントAの処理において、getKind
関数の戻り値であるstring
型の値は一時オブジェクトに格納され、そのオブジェクトはAの文が終了するまで寿命を持ちます。したがって、const char *
型にキャストされた文字列"cat"
が標準出力に表示されます。ところがコメントBの処理の場合、temp
に値が格納された文が終了した時点でgetKind
関数の戻り値である一時オブジェクトが寿命を失います。したがって、Bのprintf
が呼ばれる時点でtemp
の指すポインタは無効になっており、不正アクセスとなります。
また、C++が標準化される前は一時オブジェクトの寿命についての規定がなかったため、その頃に作成されたコンパイラでは動作するが、最新のコンパイラでは動作しないケースも考えられます。上記のプログラムもコンパイラが一時オブジェクトを破棄するタイミングがブロックの最後であれば何の不具合もなく動作します。このようなケースはまれだと考えて良いと思いますが、私の周辺で実際に起きたことがありました。
上記は、一時オブジェクトに関する知識不足の例として挙げましたが、そもそも関数の引数や戻り値が、実行時にメモリ上でどのように扱われるのかを知らないプログラマは少なくありません。そのようなプログラマの場合は、一時オブジェクトの存在について想像することすらできないかもしれませんね。ここでは割愛しますが、参照と一時オブジェクトについて注意が必要な事柄は、他にもあります。正しい知識を把握せずにむやみに参照を多用すると、その罠にはまらないとも限りません。