*C++ワンポイントレッスン Lesson-1 i++ と ++i の違い [#mad17e5b] **前置きと後置きの演算子 [#sbc8cbac] -C++には、C言語と同様の変数をインクリメント・デクリメントする演算子(++、--)がある。 -C言語の時は、おもに整数,ポインタ変数などに対し、アセンブラ命令のインクリメント・デクリメントに相当する命令を直接記述することにより高速で効率の良いコード生成を可能としてきた。 -C++では、オブジェクトの演算子をユーザーが定義できるようになったため、演算子の持つ特性と役割が大きく拡張された。 **前置き++と後置き++の違い <レジスタ変数時> [#h5bea32b] -C言語Cの時と同様に、レジスタ変数における前置きと後置きのコード生成を考える。 -C言語の時と同様に、レジスタ変数における前置きと後置きのコード生成を考える。 ''◆ C++での記述'' if (i++ < 10) Hoge(i) ''◆ 生成されるアセンブラコード'' mov ebx, eax inc eax cmp ebx, 10 jnc L1 push eax call Hoge L1: ''◆ C++での記述'' if (++i < 11) Hoge(i); ''◆ 生成されるアセンブラコード'' inc eax cmp eax, 11 jnc L1 push eax call Hoge L1: ***前置き++と後置き++の違い <レジスタ変数時> 結果 [#a1e17196] -前置き++の場合のデメリット --命令が1つされる。(メモリの増加、速度の低下) --レジスタ変数が1つ消費される。(最適化の効率低下) --コンパイル時間の増加。(最適化の余地が多いため、最適化ルーチンのコストが増加する) ※実際には、この程度のコードならほとんどのコンパイラは最適化が可能なので、実行時の差異は生じません。~ ※生成されるアセンブラはイメージです。実際にはコンパイラによって生成されるコードは異なります。~ **前置き++と後置き++の違い <クラスオブジェクト時> [#gb7a7472] -クラスオブジェクトでオーバーライドされた演算子における前置きと後置きのコード生成を考える。 ''◆ C++での記述 (後置き++の場合)'' CFoo foo; if (foo++ < 10) Hoge(foo) ''◆ インライン展開されるコード'' CFoo tmp = foo; foo.m_value.plus1(); foo.m_value.plus1(); // m_valueをインクリメントする関数 if (CFoo::operator < (tmp.m_value, 10)) { Hoge(foo) } -- foo++は、if文の成否にかかわらず実行されなければならない。 -- if文の比較(< 10)は、foo++を実行する前の状態をif文で比較する必要がある。 -- コンパイラは、まずfooの状態を記憶し、++fooを実行してからif文の処理を行う。 ''◆ C++での記述 (前置き++の場合)'' CFoo foo; if (++foo < 11) Hoge(foo); ''◆ インライン展開されるコード'' if (CFoo::operator < (foo.m_value.plus1() , 11)) { Hoge(foo) } -- if文の実行は、++fooの後の値を参照するため、プログラム通りの手順で処理される。 -- 不要なオブジェクトの生成とコピーが無いため、高速でコードサイズも少なくて済む。 ***前置き++と後置き++の違い <クラスオブジェクト時> 実装 [#paa00d8b] CFooの実装は以下のようになる。 // CFooのインスタンスとなるオブジェクト struct CValue { CValue & plus1(); } bool operator < (const CValue&, int); int hohoho; CValue & plus1() { ++hohoho; } }; bool operator < (const CValue& v, int i) { return v.hohoho < i; } struct CFoo { CValue m_value; CValue m_value; // オペレータ++でインクリメントさせたいオブジェクト CFoo() {} bool operator < (int n) { return m_value < n; } CFoo& operator ++() { m_value .plus1(); return *this; } CFoo operator ++(int) { CFoo tmp = *this; //<- オブジェクトの生成とコピーが発生 m_value .plus1(); return tmp; //<- オブジェクトの生成とコピーが発生 } } ***前置き++と後置き++の違い <クラスオブジェクト時> 結果 [#xd903d79] -前置き++時のデメリット --CFooのオブジェクトの生成の追加 --CFooオブジェクトの代入(コピー)の追加(インライン展開されない場合は2回発生する) --コード量の増加 --最適化コストの増加 **前置き++と後置き++のパフォーマンス測定 [#q5ce78b8] STLのvector<int>とmap<int,int>のiteratorをインクリメント(++)する実行時間を、前置きと後置きで比較してみました。 |オブジェクト|map<> iterator++|map<> ++iterator|vector<> iterator++|vector<> ++iterator|h |実行時間(Releaseビルド)|1.300815|0.300459|0.567246|0.566541| |比率|100.00%|23.10%|100.00%|99.98%| |実行時間(Debugビルド)|1.361722|0.108110|1.203101|0.157290| |比率|100.0%|7.94%|100.00%|13.07%| - vector<int>::iteratorの場合は、int*としてコンパイルされるため全く差は出ませんが、map<int,int>::iteratorの場合は4倍以上の速度差が出ています。 - 最適化が行われないデバッグビルドにおいては、map<int,int>::iteratorは12倍以上、vector<int>でも7倍以上、前置き++のほうが高速になっています。 - つまり、最適化が効きにくい複雑なプログラムほど、後置++のロスは大きくなることが予想されます。 &size(15){''この結果を見ても、意味のない i++ を書きますか?''}; #back ※測定環境 Athlon64 3.0GHz x2 VisualStudio 2009 [[ssacontents]]