2010年11月10日
前置インクリメントと後置インクリメントの違い
C++は後置インクリメントである。
いきなり何のことだ、と思われるかもしれないが、値を増加させることを意味する++演算子の位置のことである。変数の後ろに書けば後置インクリメント、変数の前に書けば前置インクリメントと呼ばれる。すなわち、
int i = 0; i++; //これが後置インクリメント ++i; //これが前置インクリメントである。それぞれ、値を1増加させるという意味は同じものの、増加させるタイミングが異なる。後置のものは値を使った後に増加させ、前置のものは値を増加させてから使う。ゆえに、
int i = 0; std::cout << i++; は「0」を表示してからiを1にし、 int i = 0; std::cout << ++i; はiを1にしてから「1」を表示する。intが対象だとタイミングの問題だけだが、それなりに大きなclassになるとパフォーマンス上の問題にもなってくる。次のコードを見て欲しい。intを1つ保持するMyIntというclassを定義し、前置、後置それぞれのインクリメント演算子を定義している。
#include <iostream> #include <cstdlib> /* intを1つ保持するclass 前置インクリメント、後置インクリメントを定義してあり、 値を増加させることができる */ class MyInt{ public: explicit MyInt(int i) : content(i) {} MyInt(const MyInt& i) : content(i.content) {} MyInt operator++(int){ MyInt tmp = *this; content++; return tmp; } MyInt& operator++(){ content++; return *this; } int toInt() const { return content; } private: int content; }; int main(int argc, char* argv[]) { MyInt i(0); //前置インクリメントは値を増加させて返す std::cout << (++i).toInt() << std::endl; //=> 1 //後置インクリメントは値を返してから増加させる std::cout << (i++).toInt() << std::endl; //=> 1 //std::coutに渡った値は1だが、中身は2になっているということ std::cout << i.toInt() << std::endl; //=> 2 return EXIT_SUCCESS; }
動作については何も問題はない。それぞれ意図したとおりにインクリメント演算子が働いている。ここでの問題は後置インクリメントの中身である。MyInt tmp = *this;というくだりがある。これはインクリメントする前の値を保持するためだけに使われている。もしコピーコンストラクタのコストが高いclassの場合、前置インクリメントのほうが後置インクリメントよりも速く動作することになる。
C++においてclassに対してインクリメント、デクリメント演算をする機会が一番多いのはiteratorの操作であろう。このblogで載せているコードはほとんど前置形式を使っているはずだ。それは前置形式のほうがわずかにパフォーマンスが高いからである。
しかし、同じくiteratorの操作において、後置形式でなければできない重要な機能も存在する。それが「iteratorの無効化を防ぐ」という機能だ。続く。