C++11 の range-based を使用する正しい方法は何for
ですか?
どの構文を使用する必要がありますか? for (auto elem : container)
、またはfor (auto& elem : container)
またはfor (const auto& elem : container)
?それとも他の?
要素を観察するには、次の構文を使用します。
for (const auto& elem : container) // capture by const reference
オブジェクトが安価にコピーできる場合( int
s、double
s など)、少し簡略化された形式を使用できます。
for (auto elem : container) // capture by value
要素を適切に変更するには、次を使用します。
for (auto& elem : container) // capture by (non-const) reference
コンテナーが「プロキシ イテレーター」 ( などstd::vector<bool>
) を使用する場合は、次を使用します。
for (auto&& elem : container) // capture by &&
もちろん、ループ本体内で要素のローカル コピーを作成する必要がある場合は、値によるキャプチャ( for (auto elem : container)
) が適切な選択です。
コンテナ内の要素を観察することと、その場で変更することを区別してみましょう。
簡単な例を考えてみましょう:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)
cout << x << ' ';
上記のコードは、次の要素 ( int
s) を出力しvector
ます。
1 3 5 7 9
ここで、ベクトル要素が単なる整数ではなく、カスタム コピー コンストラクターなどを使用した、より複雑なクラスのインスタンスである別のケースを考えてみましょう。
// A sample test class, with custom copy semantics.
class X
{
public:
X()
: m_data(0)
{}
X(int data)
: m_data(data)
{}
~X()
{}
X(const X& other)
: m_data(other.m_data)
{ cout << "X copy ctor.\n"; }
X& operator=(const X& other)
{
m_data = other.m_data;
cout << "X copy assign.\n";
return *this;
}
int Get() const
{
return m_data;
}
private:
int m_data;
};
ostream& operator<<(ostream& os, const X& x)
{
os << x.Get();
return os;
}
この新しいクラスで上記のfor (auto x : v) {...}
構文を使用すると、次のようになります。
vector<X> v = {1, 3, 5, 7, 9};
cout << "\nElements:\n";
for (auto x : v)
{
cout << x << ' ';
}
出力は次のようになります。
[... copy constructor calls for vector<X> initialization ...] Elements: X copy ctor. 1 X copy ctor. 3 X copy ctor. 5 X copy ctor. 7 X copy ctor. 9
出力から読み取ることができるため、範囲ベースの for ループ反復中にコピー コンストラクターの呼び出しが行われます。
これは、コンテナーから要素を値
( の部分) でキャプチャしているためです。auto x
for (auto x : v)
これは非効率的なコードです。たとえば、これらの要素が のインスタンスである場合、ヒープ メモリの割り当てを行うことができ、メモリ マネージャへの高価なトリップが発生するなどです。コンテナ内の要素を観察std::string
したいだけであれば、これは役に立ちません。
したがって、より良い構文が利用可能です:参照によるconst
キャプチャ、つまりconst auto&
:
vector<X> v = {1, 3, 5, 7, 9};
cout << "\nElements:\n";
for (const auto& x : v)
{
cout << x << ' ';
}
出力は次のようになります。
[... copy constructor calls for vector<X> initialization ...] Elements: 1 3 5 7 9
偽の (そして潜在的に高価な) コピー コンストラクター呼び出しなし。
そのため、コンテナ内の要素を監視する場合(つまり、読み取り専用アクセスの場合)、 、 などの単純で安価にコピーできる型については、次の構文で問題ありません。int
double
for (auto elem : container)
const
それ以外の場合、参照によるキャプチャは、一般的なケースではより適切であり、役に立たない (そして潜在的に高価な) コピー コンストラクターの呼び出しを回避します。
for (const auto& elem : container)
range-based を使用してコンテナ内の要素を変更したい場合for
、上記for (auto elem : container)
とfor (const auto& elem : container)
構文は間違っています。
実際、前者の場合、元の要素のコピーelem
を保存するため、それに対して行われた変更は単に失われ、コンテナーに永続的に保存されません。たとえば、次のようになります。
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v) // <-- capture by value (copy)
x *= 10; // <-- a local temporary copy ("x") is modified,
// *not* the original vector element.
for (auto x : v)
cout << x << ' ';
出力は最初のシーケンスです。
1 3 5 7 9
代わりに、使用しようとするfor (const auto& x : v)
とコンパイルに失敗します。
g++ は次のようなエラー メッセージを出力します。
TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x' x *= 10; ^
この場合の正しいアプローチは、非const
参照によるキャプチャです。
vector<int> v = {1, 3, 5, 7, 9};
for (auto& x : v)
x *= 10;
for (auto x : v)
cout << x << ' ';
出力は(予想どおり):
10 30 50 70 90
このfor (auto& elem : container)
構文は、より複雑な型に対しても機能しますvector<string>
。
vector<string> v = {"Bob", "Jeff", "Connie"};
// Modify elements in place: use "auto &"
for (auto& x : v)
x = "Hi " + x + "!";
// Output elements (*observing* --> use "const auto&")
for (const auto& x : v)
cout << x << ' ';
出力は次のとおりです。
Hi Bob! Hi Jeff! Hi Connie!
がありvector<bool>
、上記の構文を使用して、その要素の論理ブール状態を反転したいとします。
vector<bool> v = {true, false, false, true};
for (auto& x : v)
x = !x;
上記のコードはコンパイルに失敗します。
g++ は次のようなエラー メッセージを出力します。
TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::referen ce {aka std::_Bit_reference}' for (auto& x : v) ^
問題は、std::vector
テンプレートが に特化しており、スペースを最適化するために s をパックするbool
実装を備えていることです(各ブール値は 1 ビット、1 バイトに 8 つの「ブール」ビットで格納されます)。bool
そのため (単一のビットへの参照を返すことはできないため)、
vector<bool>
いわゆる「プロキシ イテレータ」パターンを使用します。「プロキシ イテレータ」は、逆参照されたときに通常の を生成せず、代わりに に変換可能なプロキシ クラスである一時オブジェクトbool &
を(値によって) 返すイテレータです。(こちらの StackOverflowのこの質問と関連する回答も参照してください。)bool
の要素をその場で変更するにはvector<bool>
、新しい種類の構文 ( を使用auto&&
) を使用する必要があります。
for (auto&& x : v)
x = !x;
次のコードは正常に動作します。
vector<bool> v = {true, false, false, true};
// Invert boolean status
for (auto&& x : v) // <-- note use of "auto&&" for proxy iterators
x = !x;
// Print new element values
cout << boolalpha;
for (const auto& x : v)
cout << x << ' ';
そして出力:
false true true false
このfor (auto&& elem : container)
構文は、通常の (プロキシではない) イテレータの他のケース (たとえば、 avector<int>
または a vector<string>
) でも機能することに注意してください。
(補足として、前述の「監視」構文はfor (const auto& elem : container)
、プロキシ イテレータの場合でも正常に機能します。)
上記の議論は、次のガイドラインに要約できます。
要素を観察するには、次の構文を使用します。
for (const auto& elem : container) // capture by const reference
オブジェクトが安価にコピーできる場合( int
s、double
s など)、少し簡略化された形式を使用できます。
for (auto elem : container) // capture by value
要素を適切に変更するには、次を使用します。
for (auto& elem : container) // capture by (non-const) reference
コンテナーが「プロキシ イテレーター」 ( などstd::vector<bool>
) を使用する場合は、次を使用します。
for (auto&& elem : container) // capture by &&
もちろん、ループ本体内で要素のローカル コピーを作成する必要がある場合は、値によるキャプチャ( for (auto elem : container)
) が適切な選択です。
ジェネリック コードでは、ジェネリック型T
が安価にコピーできるという仮定を立てることができないため、監視モードでは常に を使用しても安全for (const auto& elem : container)
です。
(これは潜在的にコストがかかる無駄なコピーをトリガーすることはなく、 のような安価なコピー タイプint
や、 のようなプロキシ イテレータを使用するコンテナーに対しても問題なく動作しますstd::vector<bool>
。)
さらに、変更モードで、汎用コードをプロキシ イテレータの場合にも機能させたい場合、最適なオプションはfor (auto&& elem : container)
です。(これは、 or
のような通常の非プロキシ イテレータを使用するコンテナでも問題なく機能します。)std::vector<int>
std::vector<string>
したがって、一般的なコードでは、次のガイドラインを提供できます。
要素を観察するには、次を使用します。
for (const auto& elem : container)
要素を適切に変更するには、次を使用します。
for (auto&& elem : container)
正しい手段は常に
for(auto&& elem : container)
これにより、すべてのセマンティクスの保持が保証されます。