c++ Range-based forのすすめ

c++で配列やvectorなどの要素の操作を行う際にfor文を使用することが多々あると思います。
皆さんはどのようにこのfor文を記述していますか?

c++のfor文の書き方の一つにrange-based forというfor文の書き方があります。

この書き方をマスターすれば、従来のfor文よりもより簡潔にコーディングでき、ミスも少なくなるのでぜひおすすめしたいです。

早速、range-based for文の書き方を見ていきましょう。

一般的なfor文の書き方

c++を初めて学ぶ際には、おそらく皆さんこのような書き方のfor文をよく見かけると思います。

#include <iostream>
#include <vector>

int main () {
    std::vector<int> numbers{1,2,3,4,5};
    for ( int i=0; i<numbers.size(); i++ ) {
        numbers[i]++;
    }
}

例えば、vectorの要素に全て1を加える操作を行う場合、上記のように書くことができます。
書くことはできますが、、、

コンテナの全要素に対して1を加えるというたったこれだけの操作のために、わざわざfor文で

  • iは0から   ( int i=0 )
  • iの終わりはnumbersのサイズまで   ( i<numbers.size() )
  • iは1づつインクリメントする   ( i++ )

この3つの条件を指定するのって大げさすぎると思いませんか。

range-based forは、このようなコンテナの全要素に対して処理を行いたいときに役に立つ書き方です。

range-based for文

ぜひ、コンテナの全要素に対して処理を行うときには、range-based for文を使いましょう。

※range-based for文はc++11以降で使用することができます。コンパイルの際は以下のようにc++11のコンパイルオプションを指定しましょう。

$ g++ -std=c++11 range-based_for.cpp

range-based for文のサンプルコードを以下に示します。

#include <iostream>
#include <vector>

int main () {
    std::vector<int> numbers{1,2,3,4,5};

    // 従来のfor文
    for ( int i=0; i<numbers.size(); i++ ) {
        std::cout << numbers[i] << std::endl;
    }

    // range-based for
    for ( auto& element : numbers ) {
        std::cout << element << std::endl;
    }
}

基本的なrenge-based for文の書き方は、
for ( auto& element : numbers ) {   …   }

このようになります。

auto&
この部分はコンテナ内の要素の型となります。今回、コンテナnumbersの要素の型はint型なので、int&と書くことができます。
c++11からはautoという変数の型を初期化子から推論してくれる機能が備わっているので、range-based for文ではしばしばauto&というオールマイティな書き方が用いられます。

element
forループ内で扱われる変数になります。この変数を操作することでコンテナ内の要素を操作します。
好きな名前をつけてください。

: numbers
range-based for文にて走査する配列やコンテナを指定します。この例ではnumbersというvectorを走査します。

range-based for文の3つの書き方

range-based for文は3つの書き方があります。タイプによって、元のコンテナの中身を書き換えれたり、書き換えれなかったりします。用途によって使い分けましょう。

#include <iostream>
#include <vector>

int main () {
	std::vector<int> numbers{1,2,3,4,5};

	// range-based for タイプ1
	for ( const auto& element : numbers ) {
		// element++;
		std::cout << element << std::endl;   // 1 2 3 4 5
	}

	// range-based for タイプ2
	for ( auto element : numbers ) {
		++element;
		std::cout << element << std::endl;   // 2 3 4 5 6   値が変わっているが、numbersは1 2 3 4 5のまま
	}

	// range-based for タイプ3
	for ( auto& element : numbers ) {
		++element;
		std::cout << element << std::endl;   // 2 3 4 5 6   値が変わっており、numbersも2 3 4 5 6に書き換わる
	}
}
  • タイプ1   for ( const auto& element : numbers )
    このタイプはコンテナnumbersの中身を書き換えることができない。constが付いているので当然ですね。
    取り出したelementの値を操作するような記述をするとコンパイルエラーとなります。
    サンプルコード中に存在する   // element++   の部分をアンコメントすると、コンパイルの時点でエラーとなります。
    例えば、『コンテナの全要素をcoutで出力する』といった中身を書き換える必要が無い場合に使用することで、元のコンテナの内容を変更することなく安全に全要素の中身を表示することができます。
  • タイプ2   for ( auto element : numbers )
    このタイプは取り出したelementの値は変更できるコンテナの中身を書き換えることができない
    それもそのはず。&が取れているので、elementはコピーコンストラクタによりコンテナの要素がコピーされたものであるため、そのコピーの値を操作すること自体はなんの問題もない。(しかしあくまでもコピーなので元コンテナの要素は変わらない。)
    サンプルコード中の   element++   という記述をしてもコンパイルエラーとはならない。
  • タイプ3 for ( auto& element : numbers )
    コンテナnumbersの中身を変更したいときの書き方。
    コンテナnumbersの中身を参照しているため、elementの値を操作するとnumbersの中身も当然書き換わります。
    例えば、『numbersの全要素に1を加える』といった操作をするときに使える。

それぞれに特徴があ流ので、状況に応じて使い分けてほしい。
私はよくタイプ1とタイプ3を使用する。

range-based forを使用する上での注意点

先程も注意書きがあったが、range-based forはc++11以降で使用できる機能となる。
コンパイルする際にはc++11のコンパイルオプションを指定してあげる必要があるので、そのことを覚えていてほしい。

gccの人

$ g++ -std=c++11 range-based_for.cpp

cmakeの人

add_compile_options(-std=c++11)

以上、range-based for文の使い方についてでした。