CodeZine(コードジン)

特集ページ一覧

C++による開発で陥りやすい問題点の検証

言語仕様の基礎を修得することの重要性

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2006/01/16 19:45

目次

一時オブジェクトに関する知識不足

 上の例とも関係していますが、一時オブジェクトに関する知識不足による不具合も少なからず見かけます。一時オブジェクトの話に入る前に、まずは参照について簡単に触れておきます。以下の処理をご覧ください。

#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++が標準化される前は一時オブジェクトの寿命についての規定がなかったため、その頃に作成されたコンパイラでは動作するが、最新のコンパイラでは動作しないケースも考えられます。上記のプログラムもコンパイラが一時オブジェクトを破棄するタイミングがブロックの最後であれば何の不具合もなく動作します。このようなケースはまれだと考えて良いと思いますが、私の周辺で実際に起きたことがありました。

 上記は、一時オブジェクトに関する知識不足の例として挙げましたが、そもそも関数の引数や戻り値が、実行時にメモリ上でどのように扱われるのかを知らないプログラマは少なくありません。そのようなプログラマの場合は、一時オブジェクトの存在について想像することすらできないかもしれませんね。ここでは割愛しますが、参照と一時オブジェクトについて注意が必要な事柄は、他にもあります。正しい知識を把握せずにむやみに参照を多用すると、その罠にはまらないとも限りません。

 
 

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

修正履歴

  • 2006/08/07 10:25 誤字を修正(4ページ目):【誤】parmへ7を代入 → 【正】parmへ5を代入

著者プロフィール

  • 阿部 学(アベ マナブ)

    PHS実機評価、C/Sアプリケーション開発、Javaチップ開発等を経て、2002年6月にエイムネクスト(株)に入社。 現在、FA機器の通信処理改善プロジェクト、Windowsの通信ミドルウェアを組み込み機器に移植するプロジェクトに従事。

バックナンバー

連載:匠のキャリアへ
All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5