C ++ 11의 범위 기반을 사용하는 올바른 방법은 무엇입니까? 사용해야합니까? for (auto

C ++ 11의 범위 기반을 사용하는 올바른 방법은 무엇입니까 for ?

어떤 구문을 사용해야합니까? for (auto elem : container)또는 for (auto& elem : container)for (const auto& elem : container)? 아니면 다른 것?



답변

컨테이너의 요소를 관찰 하는 것과 수정 하는 것의 구별을 시작합시다. 하는 .

요소 관찰

간단한 예를 보자.

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힙 메모리 할당을 수행 할 수 있습니다, 우리는 단지하려는 경우가 쓸모 등 메모리 관리자, 비싼 여행으로, 관찰 . 컨테이너의 요소 .

따라서 더 나은 구문을 사용할 수 있습니다 : capture by constreference , 즉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)구문은 다음과 같은 복잡한 유형에도 적용됩니다 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 최적화 공간들 (각 부울 값을 하나의 비트, 바이트 여덟 “부울”비트에 저장된다).

(이것은 하나의 비트에 대한 참조를 반환하는 것은 불가능하기 때문에) 그 때문에,
vector<bool>소위 사용 “프록시 반복자” 패턴. A “프록시 반복자는”역 참조 할 때, 않습니다 반복자입니다 없는 평범한를 얻을 수 bool &(값)을 반환하는 대신,하지만 임시 개체 A는, 프록시 클래스 로 변환을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)구문은 다른 일반 프록시 (비 프록시) 반복자에서도 작동합니다 (예 : a vector<int>또는 avector<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>or 와 같은 일반적인 비 프록시 반복자를 사용하는 컨테이너에서도 잘 작동합니다 std::vector<string>.)

따라서 일반 코드 에서 다음 지침을 제공 할 수 있습니다.

  1. 요소 를 관찰 하려면 다음을 사용하십시오.

    for (const auto& elem : container)
  2. 적절한 요소 를 수정 하려면 다음을 사용하십시오.

    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-loop의 구문 요구 사항은 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