読者です 読者をやめる 読者になる 読者になる

C++で後置インクリメントよりも前置インクリメントが多用される理由

プログラミング

追記(2015/04/19): 後置を使うべきという意見もあるようです ->
前置インクリメント vs 後置インクリメント | 闇夜のC++


C++を勉強し始めるまでは特に必要がない限り、値をインクリメントする際は前置ではなく、後置でやっているのを見かけるのが多かったのですが、C++の本や解説サイトを見ると、インクリメントが後置ではなく、前置になっているのをよく見かけます。
昨日まで単にC++のスタイル的な問題なんだろうと思っていたのですが、どうやらもっとちゃんとした理由があるようです。
IRCで話を振ったら、友達が教えてくれました。

前置インクリメントと後置インクリメント


まず、インクリメントを使った例としてfor文によるループを考えてみます。
C言語で書くと例えば以下のようになります。

for (i=0;i<n;i++) {
  ...
}

C++でも同じように書けます。

for (i=0;i<n;i++) {
  ...
}

本題からは外れますが、C++では変数の宣言をfor文の中で行うこともできます。

for (int i=0;i<n;i++) {
  ...
}

で、C++だと以下のようなコードをよく見かけます。

for (int i=0;i<n;++i) {
  ...
}

C++だとインクリメントを前置か後置のどちらでやるかを考慮する必要があります。

演算子オーバーロードの例


C++には演算子オーバーロードという機能があり、これを使うと指定した演算子の動作を多重定義することが可能になります。
例えば、以下のようなCounterというクラスがあるとします。

class Counter
{
public :
  Counter(int n);
private :
  int cnt;
};

Counter::Counter(int n) {
  this->cnt = n;
}

上記のコードだと、初期値を設定することしかできないので、メンバ変数cntの値をインクリメントできるようにしたいと思います。(cntの現在の値を取得できるようにしたい気もしますが、本題からは外れるので割愛します)
そのための方法としては次の二つが挙がります。

  1. cntをpublic変数にする
  2. incとか適当な名前のメンバ関数を用意してその中でインクリメントする

1だとそもそもクラス化した意味が薄れるので、どちらかと言うと2の方が妥当な方法と言えそうです。

定義
Counter Counter::inc () {
  this->cnt++;
}
使用例
Counter c(10);
c.inc();

これでも特に問題があるわけではありませんが、演算子オーバーロードでもメンバ変数cntをprivate変数にしたままインクリメントすることが可能です。

Counter c(10);
c++;
++c;

上記のコードではインクリメント演算子の対象がメンバ変数ではなく、Counterクラスのオブジェクトそのものになっています。
当然、このままだとエラーになるので、以下のコードを追加してやります。

// 前置インクリメント ++c
Counter& Counter::operator++() {
  if (this->cnt < INT_MAX) {
    this->cnt++;
  }
  return *this;
}

// 後置インクリメント c++
Counter Counter::operator++(int) {
  Counter c = *this;
  if (cnt < INT_MAX) {
    this->cnt++;
  }
  return c;
}

上記ではCouterクラスのオブジェクトに対して(前置・後置)インクリメントした際の動作を定義しています。
どちらもメンバ変数cntをインクリメントしていますが、内部の動作は微妙に違います。
これは前置インクリメントの場合、インクリメント後の値を返す必要があるのに対して、後置インクリメントの場合、インクリメント前の値を返す必要があるためです。
内部動作についてですが、前置インクリメントの処理が*this、つまりインスタンス自体を返しているのに対して、後置インクリメントの処理では内部で生成したインスタンスのコピーを返しています。
だから、前置インクリメントの方が無駄な処理がなくて少しだけ幸せになれるわけです。

イテレータの例


Counterの例では少々無理矢理感がありましたが、
イテレータを使う場合、インクリメントは頻繁に登場します。
以下は文字列の一つ一つの文字をイテレータを使って処理する例です。

std::string s("abc");
std::string::iterator sit;
for (sit=s.begin();sit!=s.end();++sit) {
  std::cout << *sit << " ";
 }
std::cout << std::endl;

まとめ


C++で後置インクリメントより前置インクリメントを見かけることが多いのは、パフォーマンスを考えてのことです。
もちろん、いつでも前置である必要はないですが、前置にした方がいい場合があるので、特に必要がない限りは習慣的に後置インクリメントよりも前置インクリメントを使うようにしているという感じでしょうか。

参考文献


STL―標準テンプレートライブラリによるC++プログラミング 第2版

STL―標準テンプレートライブラリによるC++プログラミング 第2版

  • 作者: ディビッド・R.マッサー,アトゥルサイニ,ギルマー・J.ダージ,David R. Musser,Atul Saini,Gillmer J. Derge,滝沢徹,牧野祐子
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2001/12
  • メディア: 単行本
  • 購入: 5人 クリック: 74回
  • この商品を含むブログ (18件) を見る

最終的なCounterクラスのコード

class Counter
{
public :
  Counter(int n);
  Counter& operator++();
  Counter operator++(int);
private :
  int cnt;
};

Counter::Counter(int n) {
  this->cnt = n;
}

Counter& Counter::operator++() {
  if (this->cnt < INT_MAX) {
    this->cnt++;
  }
  return *this;
}

Counter Counter::operator++(int) {
  Counter c = *this;
  if (cnt < INT_MAX) {
    this->cnt++;
  }
  return c;
}