Arantium Maestum

プログラミング、囲碁、読書の話題

Effective C++勉強メモ: Item 26 変数の定義はできるだけ遅らせる

古き良きCの作法だと使う変数はすべて関数の一番最初に宣言しておくものだった。

モダンなCだとその必要はないが、C++だとむしろ強く非推奨になる。

いくつかの理由がある。

一つには、変数宣言はその場でコンストラクタの実行を伴い、またスコープから出るとき必ずデストラクタの実行を伴うものだからだ。

void f ()
{
   std::string s;
   if ( ...) {
     throw e;
   }
   do_something_with(s);
}

というようなコードの場合、もし途中で例外を投げた場合sのコンストラクタとデストラクタは意味なく実行されている。

さらに

void f ()
{
   if ( ...) {
     throw e;
   }
   ...

   std::string s;
   s = some_string;
   do_something_with(s);
}

と書くと初期化してからコピー代入が行われる。それも非効率なので

void f ()
{
   if ( ...) {
     throw e;
   }
   ...

   std::string s(some_string);
   do_something_with(s);
}

初期化のパラメータとして値をいれることが推奨される。なので1)使う直前、かつ2)初期化のためのパラメータが定まってから、変数を定義するのがベスト。

変数をループの中で使う時は少しややこしい。

Obj x;
while (int i = 0; i < n; ++i) {
  y = f(i);
  ...
}

while (int i = 0; i < n; ++i) {
  Obj y(f(i));
  ...
}

のどちらがいいか。当然コンストラクタとデストラクタが一回しか呼ばれない前者だ、と思うかもしれないが、前者はそのかわりコピー代入がn回行われており、一概に効率がいいとは言えない。

さらに前者はObj xがループのスコープの外に出ている。ループ内で繰り返し代入されるような変数は大抵ループ外での意味は持たないことが多いので、プログラムの意味的には変数が外部にリークしない後者のほうがすぐれている。

Scott Meyersの忠告は、コンストラクタ+デストラクタのコストがコピー代入よりも有意に高く、ループが大きい場合のみ前者、それ以外はデフォルトで後者を使うべき、とのこと。