HOWTO · C++

C++ でのプリインクリメント VS ポストインクリメント演算子

この記事では、C++ でプリインクリメントとポストインクリメントを使用する方法を示します。

このページの内容

C++ の組み込み算術型の変数では、++x も x++ も x に 1 を加えます。違いは式が返す値です。前置インクリメントは更新後の値を返し、後置インクリメントは更新前の値を返します。周囲の式でどちらの値が必要かに応じて選びましょう。ユーザー定義型では、オーバーロードされたインクリメント演算子の動作は各定義によりますが、慣例として前置と後置で同様の違いを設けます。

C++ における前置インクリメントと後置インクリメントの違い

組み込み算術型では、++x は式の結果を返す前に x を更新します。x++ は古い値を返し、演算の一部としてインクリメントを完了します。どちらの場合も、変数は一度だけ変化します。オーバーロードされた演算子では細部が異なる場合がありますが、慣例として同じ前置・後置の違いに従います。

次の例を実行します。

#include <iostream>

int main() {
    int value = 4;
    int previous = value++;
    int current = ++value;

    std::cout << previous << ' ' << current << ' ' << value << '\n';
}
4 6 6

previous には最初のインクリメント前の値が入り、current には 2 回目のインクリメント後の値が入ります。これは value の最終値でもあります。

ループ本体内のどこでインクリメントするかによって、本体が出力する値は変わります。最初のループでは i++ が出力式なので、現在の値を出力してから i をインクリメントします。2 つ目のループでは、出力式より先に ++j が実行されるため、本体は更新後の値を出力します。

#include <iostream>

int main() {
    int i = 0;
    while (i < 5) {
        std::cout << i++ << '\n';
    }
    std::cout << "final i = " << i << '\n';
}
#include <iostream>

int main() {
    int j = 0;
    while (j < 5) {
        std::cout << ++j << '\n';
    }
    std::cout << "final j = " << j << '\n';
}

最初のループは 0、1、2、3、4 の順に出力し、その後 final i = 5 を出力します。2 つ目は 1、2、3、4、5 の順に出力し、その後 final j = 5 を出力します。final i と final j は、出力に表示される英語のラベルです。どちらのループもカウンターは 0 から始まり、本体が 5 回実行され、最終値は 5 になります。この例ではループ本体内でインクリメントしています。一方、for ループの反復式にインクリメントを書くと、本体の実行後に処理されます。その結果が使われない場合、++k を k++ に置き換えても反復順序は変わりません。for (int k = 0; k < 5; ++k) も k++ を使う形も、0、1、2、3、4 を順に処理し、最後に k は 5 になります。

1 つの式で同じ変数を変更しながら読み取らない

同じ変数のインクリメントと、その変数の別の読み取りを、順序付けされていない同一の式にまとめてはいけません。たとえば k++ * k は C++ では未定義動作です。変更ともう一方の読み取りの間に順序が定まっていません。その結果は予測できず、有効な出力例として扱えません。

古い値と次の値を掛ける計算が目的なら、手順を明示して順序付けます。k を初期化し、その値を old に保存してから k をインクリメントし、最後に old * k を計算します。

別々の文にすることで、k をインクリメントする前に古い値を保存し、その後、保存した値と更新後の値を掛けられます。次の例を実行します。

#include <iostream>

int main() {
    int k = 10;
    int old = k;
    ++k;
    int product = old * k;

    std::cout << product << '\n';
}
110

前置インクリメントと後置インクリメントの使い分け

int previous = value++; のように、式で古い値が必要な場合は後置インクリメントを使います。更新後の値が必要な場合や、ループの反復式のように式の値を使わない場合は、前置インクリメントを使います。

イテレーターでは、古い値を必要としないときに前置インクリメントを使うのが一般的な慣例です。ユーザー定義イテレーターのオーバーロード演算子の動作は実装によりますが、慣例として組み込み型の前置・後置の違いに倣います。後置インクリメントでは、以前のイテレーターをコピーして保持し、そのコピーを返すことがあります。コピーにコストがかかるイテレーター型では、前置インクリメントならその処理を避けられる場合があります。ただし、これだけで特定のプログラムに測定可能な速度差があるとは言えません。生成される処理は、コンパイラーの最適化やイテレーターの実装によって変わります。

まとめ

どちらの形式も変数を一度インクリメントします。前置インクリメントは更新後の値を返し、後置インクリメントは更新前の値を返します。式で必要な値が伝わる形式を選びましょう。イテレーターでは、以前の値を使わない場合は前置インクリメントを使うのが一般的ですが、必ず速くなるとは限りません。

よくある質問

前置インクリメントと後置インクリメントの主な違いは何ですか?

組み込み算術型では、++x は先に更新して更新後の値を返します。x++ は更新前の値を返し、その後インクリメントを完了します。オーバーロードされた演算子の動作は定義によりますが、慣例として前置と後置で同様の違いを設けます。

ループではどちらの形式も使えますか?

はい。for ループの反復式では結果を使わないことが多いため、++i を i++ に置き換えても、そのループの反復順序は変わりません。

C++ ではどちらの形式が速いですか?

すべての式や型に当てはまる一律の速度差はありません。イテレーターやユーザー定義型では、後置インクリメントで一時的なコピーが発生することがありますが、最適化によって不要な処理が取り除かれる場合もあります。必要な値と意図の明確さを基準に選び、性能を論じるときは実際の処理を測定してください。

後置インクリメントはどのような場合に便利ですか?

別の変数に代入する場合のように、インクリメント前の値が式で必要なときに使います。

前置インクリメントと後置インクリメントは、どの言語でも同じ動作をしますか?

いいえ。表記の似た演算子でも、言語によって規則が異なることがあります。使用する言語の意味規則を確認してください。ここでの説明は C++ に関するものです。