232

C++11 の range-based を使用する正しい方法は何forですか?

どの構文を使用する必要がありますか? for (auto elem : container)、またはfor (auto& elem : container)またはfor (const auto& elem : container)?それとも他の?

4

4 に答える 4

442

TL;DR: 次のガイドラインを考慮してください。

  1. 要素を観察するには、次の構文を使用します。

    for (const auto& elem : container)    // capture by const reference
    
    • オブジェクトが安価にコピーできる場合( ints、doubles など)、少し簡略化された形式を使用できます。

        for (auto elem : container)    // capture by value
      
  2. 要素を適切に変更するには、次を使用します

    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 << ' ';

上記のコードは、次の要素 ( ints) を出力し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 xfor (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

偽の (そして潜在的に高価な) コピー コンストラクター呼び出しなし。

そのため、コンテナ内の要素を監視する場合(つまり、読み取り専用アクセスの場合)、 、 などの単純で安価にコピーできる型については、次の構文で問題ありません。intdouble

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)、プロキシ イテレータの場合でも正常に機能します。)

概要

上記の議論は、次のガイドラインに要約できます。

  1. 要素を観察するには、次の構文を使用します。

    for (const auto& elem : container)    // capture by const reference
    
    • オブジェクトが安価にコピーできる場合( ints、doubles など)、少し簡略化された形式を使用できます。

        for (auto elem : container)    // capture by value
      
  2. 要素を適切に変更するには、次を使用します

    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>

したがって、一般的なコードでは、次のガイドラインを提供できます。

  1. 要素を観察するには、次を使用します。

    for (const auto& elem : container)
    
  2. 要素を適切に変更するには、次を使用します

    for (auto&& elem : container)
    
于 2013-04-10T13:20:04.763 に答える
4

正しい手段は常に

for(auto&& elem : container)

これにより、すべてのセマンティクスの保持が保証されます。

于 2013-04-10T19:35:24.440 に答える