C ++ 11の範囲ベースの正しい使用方法は何ですか?


211

C ++ 11の範囲ベースを使用する正しい方法は何forですか?

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


6
関数の引数と同じ考慮事項が適用されます。
Maxim Egorushkin 2013

3
実際、これは範囲ベースのforとはほとんど関係ありません。同じことは、どんなことにも言えますauto (const)(&) x = <expr>;
Matthieu M.

2
@MatthieuM:もちろん、これは範囲ベースの処理に大きく関係しています!いくつかの構文を見て、使用するフォームを選択できない初心者を考えてみてください。「Q&A」のポイントは、いくつかの光を当てて、いくつかのケースの違いを説明しようとすることでした(そして、うまくコンパイルできるが、ディープコピーが役に立たないために非効率的であるようなケースについて議論します)。
Mr.C64 2013

2
@ Mr.C64:私に関する限り、これはauto一般に、範囲ベースのforよりもに関連しています。範囲ベースなしで完全に使用できますautofor (int i: v) {}完全に元気です。もちろん、回答で提起するポイントのほとんどは、タイプよりもauto... に関係している可能性がありますが、質問からは、問題点がどこにあるのか明確ではありません。個人的に、私はauto質問から削除することを争います。またはauto、タイプを使用するか明示的に名前を付けるかに関わらず、質問は値/参照に焦点を当てていることを明示的にするかもしれません
Matthieu M.

1
@MatthieuM .:私は、タイトルを変更したり、質問をより明確にするような形式で編集したりできます...繰り返しますが、私の焦点は、構文の範囲ベースのいくつかのオプションを議論することでした(コンパイルするコードを示していますが、非効率的、コンパイルに失敗したコードなど)、C ++ 11範囲ベースのforループにアプローチしている誰か(特に初心者レベル)にガイダンスを提供しようとしています。
Mr.C64 2013

回答:


389

コンテナ内の要素を観察することと、要素を適切に変更することを区別してみましょう。

要素を観察する

簡単な例を考えてみましょう:

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

コンテナの要素を変更する

範囲ベースのを使用してコンテナの要素を変更する場合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)、プロキシイテレータの場合にもうまく機能します。)

概要

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

  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)です。
(これは次のように、通常の非プロキシ・イテレータを使用してコンテナにうまくも動作しますstd::vector<int>std::vector<string>。)

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

  1. 観察要素を、使用します。

    for (const auto& elem : container)
  2. 以下のために修正する場所、使用中の要素を:

    for (auto&& elem : container)

7
一般的なコンテキストに対するアドバイスはありませんか?:(
R.マルティーニョフェルナンデス

11
なぜいつも使用しないのauto&&ですか?ありconst auto&&ますか?
マーティンBa

1
ループ内に実際にコピーが必要な場合が欠けていると思いますか?
juanchopanza 2013

6
プロキシ反復子「『コンテナが使用する場合は、』」 - 、あなたは知っている(一般的なコードの事例ではないかもしれません)プロキシイテレータを「それは使用しています」。だから、私は最高のものが確かauto&&にカバーできるので、本当にそうだと思いますauto&
Christian Rau 2013

5
ありがとうございます。C#プログラマーにとって、これは範囲の構文といくつかのヒントに対する「クラッシュコースの紹介」として非常に優れていました。+1。
AndrewJacksonZA 2013

17

何もありません正しい方法を使用する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;

}

コメントが言うように、ただ読むために。そして、それはそれについてです、適切に使用されるとき、すべては「正しい」です。


2
私は、サンプルコードをコンパイルして(ただし非効率的です)、コンパイルに失敗し、その理由を説明して、いくつかのガイダンスを提供し、いくつかの解決策を提案するつもりでした。
Mr.C64 2013

2
@ Mr.C64おお、ごめんなさい-これはFAQタイプの質問の1つであることに気づきました。このサイトは初めてです。お詫び!あなたの答えは素晴らしいです、私はそれを賛成しました-しかし、それの要点望んでいる人々のためにより簡潔なバージョンを提供したかったのです。うまくいけば、私は侵入しません。

1
@ Mr.C64 OPも質問に答えることの問題は何ですか?これは、もう1つの有効な答えです。
mfontanini 2013

1
@mfontanini:誰かが私の答えよりも優れた答えを投稿しても問題はありません。最終的な目的は、コミュニティーに質の高い貢献をすることです(特に、C ++が提供するさまざまな構文やオプションの前で一種の迷いを感じる初心者向け)。
Mr.C64 2013

4

正しい手段は常に

for(auto&& elem : container)

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


6
しかし、コンテナが変更可能な参照のみを返し、ループでそれらを変更したくないことを明確にしたい場合はどうなりますか?その後auto const &、私の意図を明確にするために使用すべきではありませんか?
RedX 2013

@RedX:「変更可能な参照」とは何ですか?
オービットのライトネスレース2013年

2
@RedX:参照がになることはなくconst、変更されることもありません。とにかく、あなたへの私の答えは「はい」です。
オービットのライトネスレース2013年

4
これはうまくいくかもしれませんが、C64の上記の優れた包括的な回答によって与えられた、より微妙で考慮されたアプローチと比較して、これは不十分なアドバイスだと思います。最小公分母に還元することは、C ++の目的ではありません。
Jack Aidley、2013

6
この言語進化の提案は、この「貧しい」答えに同意します:open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3853.htm
Luc

1

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 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.