1. ホーム
  2. c++

[解決済み】C++11のrange-based forの正しい使い方は?

2022-04-30 15:53:08

質問

C++11の範囲ベースの for ?

どのような構文にすればよいのでしょうか? for (auto elem : container) , または for (auto& elem : container) または for (const auto& elem : container) ? それとも他の?

解決方法は?

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

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

    for (const auto& elem : container)    // capture by const reference
    
    
    • もし、オブジェクトが コピー代が安い (例えば int s, double sなど)。 というように、少し簡略化した形で使用することが可能です。

        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) )を選択するとよいでしょう。


詳細なディスカッション

の区別を始めましょう。 観察する コンテナ内の要素 vs. 修正 をそのまま使用します。

要素の観察

簡単な例で考えてみましょう。

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 , ヒープメモリの割り当てを行うことができ、メモリマネージャへの負荷が高くなります。 これは,単に 見る コンテナ内の要素

そこで、より良い構文が用意されています: capture によって 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;
            ^

この場合の正しいアプローチは、nonでキャプチャすることです。 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 テンプレートは 専門的 に対して bool である。 という実装があります。 パック その bool を使用してスペースを最適化します(各ブーリアン値は 1ビットに格納され、1バイトに8ビットのboolean"boolean;が格納されます)。

そのため(1ビットへの参照を返すことができないため)。 vector<bool> は、いわゆる プロキシイテレータです。 というパターンがあります。 プロキシイテレータとは、参照されたときに ない は 通常の bool & を返しますが、その代わりに(値で 一時的なオブジェクト , というもので、これは プロキシクラス に変換することができます。 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> ).

(余談ですが、前述の "observing" の構文で for (const auto& elem : container) はプロキシイテレータの場合にも問題なく動作します)。

概要

以上の議論をまとめると、次のような指針になります。

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

    for (const auto& elem : container)    // capture by const reference
    
    
    • もし、オブジェクトが コピー代が安い (例えば int s, double sなど)。 というように、少し簡略化した形で使用することが可能です。

        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) .

(のような、通常の非プロキシ型イテレータを使用するコンテナでも問題なく動作します)。 std::vector<int> または std::vector<string> .)

そこで 汎用コード は、次のような指針を示すことができる。

  1. について 観察 要素を使用します。

    for (const auto& elem : container)
    
    
  2. について モディファイ を使うと、その場にある要素を使うことができます。

    for (auto&& elem : container)