C++ 11の範囲ベースのfor
を使用する正しい方法は何ですか?
どの構文を使用する必要がありますか? for (auto elem : container)
、またはfor (auto& elem : container)
またはfor (const auto& elem : container)
?または他の?
observingコンテナ内の要素とmodifyingの要素を区別し始めましょう。
簡単な例を考えてみましょう:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)
cout << x << ' ';
上記のコードは、int
の要素(vector
s)を出力します。
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
出力から読み取ることができるため、copy constructor呼び出しは、ループの繰り返しの範囲ベースで行われます。
これは、キャプチャコンテナの要素by value(for (auto x : v)
のauto x
部分)であるためです。 。
これは非効率コードです。これらの要素がstd::string
のインスタンスである場合、メモリマネージャーへの負荷の高いトリップなどでヒープメモリの割り当てを行うことができます。これは、コンテナ内の要素をobserveだけにしたい場合は役に立ちません。
そのため、より良い構文が利用可能です:captureby const
reference、つまり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
偽の(および潜在的に高価な)コピーコンストラクター呼び出しなし。
そのため、コンテナ内のobserving要素(つまり、読み取り専用アクセス)の場合、単純なcheap-to -copyタイプ(int
、double
など):
for (auto elem : container)
そうでない場合、const
参照によるキャプチャーは一般的なケースの方が優れており、無駄な(そして潜在的に高価な)コピーコンストラクター呼び出しを回避します。
for (const auto& elem : container)
範囲ベースのfor
を使用してコンテナ内の要素をmodifyにしたい場合、上記のfor (auto elem : container)
およびfor (const auto& elem : container)
構文は間違っています。
実際、前者の場合、elem
は元の要素のcopyを格納するため、それに加えられた変更は失われ、コンテナに永続的に格納されません。例:
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
テンプレートがspecializedbool
であり、実装がpacksbool
sでスペースを最適化することです(各ブール値は1ビット、8つの「ブール」ビットで格納されます)バイトで)。
そのため(単一ビットへの参照を返すことができないため)、vector<bool>
はいわゆる "proxy iterator"パターンを使用します。 「プロキシイテレータ」は、逆参照されるとnotが通常のbool &
を生成しますが、代わりに(値によって)temporary objectを返すイテレータです。 プロキシクラスbool
に変換可能 。 (StackOverflowの この質問と関連する回答 も参照してください。)
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)
構文は、通常の(非プロキシ)イテレーターの他のケースでも機能することに注意してください(例えば、vector<int>
またはvector<string>
)。
(補足として、前述のfor (const auto& elem : container)
の「監視」構文は、プロキシイテレータの場合にも正常に機能します。)
上記の説明は、次のガイドラインに要約できます。
observing要素については、次の構文を使用します。
for (const auto& elem : container) // capture by const reference
オブジェクトがコピーするのが安い(int
s、double
sなど)の場合、わずかに簡略化された形式を使用できます。
for (auto elem : container) // capture by value
配置されている要素をmodifyingするには、次を使用します:
for (auto& elem : container) // capture by (non-const) reference
コンテナが"proxy iterators"(std::vector<bool>
など)を使用する場合、次を使用します。
for (auto&& elem : container) // capture by &&
もちろん、ループ本体内の要素のローカルコピーを作成する必要がある場合、by by value( for (auto elem : container)
)は良い選択です。
generic codeでは、ジェネリック型T
のコピーが安価であると仮定できないため、observingモードでは安全です常にfor (const auto& elem : container)
を使用します。
(これは潜在的に高価な無用なコピーをトリガーしません。int
のようなコピーが安価なタイプや、std::vector<bool>
のようなプロキシイテレータを使用するコンテナでもうまく動作します。)
さらに、modifyingモードで、プロキシイテレータの場合にもgeneric codeが機能するようにしたい場合、最良のオプションはfor (auto&& elem : container)
。
(これは、std::vector<int>
やstd::vector<string>
などの通常の非プロキシイテレーターを使用するコンテナーでも正常に機能します。)
そのため、汎用コードでは、次のガイドラインを提供できます。
observing要素については、以下を使用します:
for (const auto& elem : container)
配置されている要素をmodifyingするには、次を使用します:
for (auto&& elem : container)
for (auto elem : container)
、またはfor (auto& elem : container)
またはfor (const auto& elem : container)
を使用する正しい方法はありません。あなたが望むものを表現するだけです。
それについて詳しく説明させてください。散歩しましょう。
for (auto elem : container) ...
これは次の構文糖衣です:
for(auto it = container.begin(); it != container.end(); ++it) {
// Observe that this is a copy by value.
auto elem = *it;
}
コンテナにコピーするのが安価な要素が含まれている場合、これを使用できます。
for (auto& elem : container) ...
これは次の構文糖衣です:
for(auto it = container.begin(); it != container.end(); ++it) {
// Now you're directly modifying the elements
// because elem is an lvalue reference
auto& elem = *it;
}
たとえば、コンテナ内の要素に直接書き込む場合に使用します。
for (const auto& elem : container) ...
これは次の構文糖衣です:
for(auto it = container.begin(); it != container.end(); ++it) {
// You just want to read stuff, no modification
const auto& elem = *it;
}
コメントが言っているように、ただ読んでください。そしてそれはそれについてです、適切に使用された場合、すべてが「正しい」です。
正しい手段は常に
for(auto&& elem : container)
これにより、すべてのセマンティクスの保持が保証されます。
Range-forループの最初の動機はコンテナの要素を簡単に反復することだったかもしれませんが、構文は純粋にコンテナではないオブジェクトに対しても役立つほど汎用的です。
Forループの構文要件は、range_expression
がいずれかの関数としてbegin()
およびend()
をサポートすることです。評価される型のメンバー関数として、または非メンバー関数として型のインスタンスを取得します。
考案された例として、次のクラスを使用して数値の範囲を生成し、その範囲を反復処理できます。
struct Range
{
struct Iterator
{
Iterator(int v, int s) : val(v), step(s) {}
int operator*() const
{
return val;
}
Iterator& operator++()
{
val += step;
return *this;
}
bool operator!=(Iterator const& rhs) const
{
return (this->val < rhs.val);
}
int val;
int step;
};
Range(int l, int h, int s=1) : low(l), high(h), step(s) {}
Iterator begin() const
{
return Iterator(low, step);
}
Iterator end() const
{
return Iterator(high, 1);
}
int low, high, step;
};
次のmain
関数を使用すると、
#include <iostream>
int main()
{
Range r1(1, 10);
for ( auto item : r1 )
{
std::cout << item << " ";
}
std::cout << std::endl;
Range r2(1, 20, 2);
for ( auto item : r2 )
{
std::cout << item << " ";
}
std::cout << std::endl;
Range r3(1, 20, 3);
for ( auto item : r3 )
{
std::cout << item << " ";
}
std::cout << std::endl;
}
次の出力が得られます。
1 2 3 4 5 6 7 8 9
1 3 5 7 9 11 13 15 17 19
1 4 7 10 13 16 19