コンテナ内の要素を観察することと、要素を適切に変更することを区別してみましょう。
要素を観察する
簡単な例を考えてみましょう:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)
cout << x << ' ';
上記のコードは、要素(int
)をに出力します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)
コンテナの要素を変更する
範囲ベースのを使用してコンテナの要素を変更する場合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)
構文は、たとえばaを検討するなど、より複雑な型でも機能します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
テンプレートをに特化しbool
、sをパックしてbool
スペースを最適化する実装になっていることです(各ブール値は1ビットに格納され、8バイトの「ブール」ビットがバイトに格納されます)。
そのため(単一ビットへの参照を返すことができない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)
構文は、通常の(非プロキシ)イテレータの他のケースでも機能することに注意してください(例:a vector<int>
またはa vector<string>
)。
(補足として、前述の「監視」の構文はfor (const auto& elem : container)
、プロキシイテレータの場合にもうまく機能します。)
概要
上記の説明は、次のガイドラインに要約できます。
観察要素を、次の構文を使用します。
for (const auto& elem : container) // capture by const reference
以下のために修正する場所、使用中の要素を:
for (auto& elem : container) // capture by (non-const) reference
もちろん、ループ本体内の要素のローカルコピーを作成する必要がある場合は、値(for (auto elem : container)
)でキャプチャすることをお勧めします。
ジェネリックコードに関する追加の注意事項
では、一般的なコード、私たちはジェネリック型について仮定することはできませんので、T
コピーに安価であることを、して観察モードを常に使用しても安全ですfor (const auto& elem : container)
。
(これは、潜在的に高価な役に立たないコピーをトリガーすることはなく、などの安価なコピータイプでもint
、などのプロキシイテレータを使用するコンテナでも問題なく機能しますstd::vector<bool>
。)
さらに、変更モードで、プロキシイテレータの場合にも汎用コードを機能させたい場合、最良のオプションはfor (auto&& elem : container)
です。
(これは次のように、通常の非プロキシ・イテレータを使用してコンテナにうまくも動作しますstd::vector<int>
かstd::vector<string>
。)
したがって、汎用コードでは、次のガイドラインを提供できます。
観察要素を、使用します。
for (const auto& elem : container)
以下のために修正する場所、使用中の要素を:
for (auto&& elem : container)