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


211

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

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


6
함수 인수와 동일한 고려 사항이 적용됩니다.
Maxim Egorushkin

3
실제로 이것은 범위 기반과 거의 관련이 없습니다. 어느 것도 동일하게 말할 수 있습니다 auto (const)(&) x = <expr>;.
Matthieu M.

2
@MatthieuM :이은이 많은 과는 물론, 대한 범위를 기반! 몇 가지 구문을보고 사용할 양식을 선택할 수없는 초보자를 고려하십시오. "Q & A"의 요점은 약간의 빛을 발산하고 일부 사례의 차이점을 설명하는 것입니다 (그리고 잘 컴파일되지만 쓸모없는 딥 카피 등으로 인해 비효율적 인 사례에 대해 논의).
Mr.C64

2
@ Mr.C64 : 내가 아는 한, 이것은 auto범위 기반보다 일반적으로 와 관련이 있습니다 . 당신은 전혀없이 범위 기반을 완벽하게 사용할 수 있습니다 auto! for (int i: v) {}완벽하게 괜찮습니다. 물론 대답에서 제기하는 대부분의 점 은 유형보다 유형과 더 관련이있을 수 auto있지만 질문에서 통증 지점이 어디에 있는지 명확하지 않습니다. 개인적으로 나는 auto질문에서 제거하기 위해 경쟁 할 것이다 . 또는 auto유형 을 사용 하거나 명시 적으로 이름을 지정 하든 , 질문은 값 / 참조에 초점을 둔다는 것을 명시 적으로 만들 수 있습니다 .
Matthieu M.

1
@MatthieuM .: 제목을 변경하거나 질문을 더 명확하게 할 수있는 형식으로 질문을 편집 할 수 있습니다 ... 다시, 초점은 구문에 대한 범위 기반 옵션 (컴파일하지만 비효율적이며 컴파일되지 않는 코드 등)이며 C ++ 11 범위 기반 for 루프에 접근하는 누군가 (특히 초보자 수준)에게 지침을 제공하려고합니다.
Mr.C64

답변:


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

따라서 더 나은 구문을 사용할 수 있습니다 : 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)

7
일반적인 상황에 대한 조언이 없습니까? :(
R. Martinho Fernandes

11
왜 항상 사용하지 auto&&않습니까? 있습니까 const auto&&?
Martin Ba

1
루프 내부에서 실제로 사본이 필요한 경우가 누락 된 것 같습니다.
juanchopanza

6
프록시 반복자 ""컨테이너 사용은 경우 "" - 그리고 당신은 알고있다 (일반적인 코드의 경우하지 않을 수 있습니다) 프록시 반복자를 "이 사용". 그래서 나는 최고가 똑같이 잘 auto&&커버되기 때문에 실제로 최고라고 생각합니다 auto&.
Christian Rau

5
감사합니다. 이는 C # 프로그래머를위한 구문과 그 범위에 대한 몇 가지 팁에 대한 "충돌 과정 소개"입니다. +1.
AndrewJacksonZA

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

2
@ Mr.C64 죄송합니다. 이것이 FAQ 유형의 질문 중 하나라는 것을 알게되었습니다. 이 사이트를 처음 사용합니다. 사과! 당신의 대답은 훌륭합니다. 나는 그것을 찬성 했습니다 . 잘만되면 나는 방해하지 않고있다.

1
@ Mr.C64 OP가 질문에 대답하는 데 무엇이 문제입니까? 또 다른 유효한 대답입니다.
mfontanini

1
@ mfontanini : 누군가가 내 것보다 더 나은 답변을 게시하면 아무런 문제가 없습니다. 마지막 목적은 커뮤니티에 양질의 기여를하는 것입니다 (특히 C ++에서 제공하는 다양한 구문과 옵션으로 인해 길을 잃은 느낌이들 수있는 초보자에게).
Mr.C64

4

올바른 방법은 항상

for(auto&& elem : container)

이것은 모든 의미론의 보존을 보장 할 것입니다.


6
그러나 컨테이너가 수정 가능한 참조 만 반환하고 루프에서 수정하지 않으려면 어떻게해야합니까? 그런 다음 auto const &의도를 명확하게하기 위해 사용해야하지 않습니까?
RedX

@RedX : "수정 가능한 참조"란 무엇입니까?
궤도에서 가벼움 레이스

2
@RedX : 참조는 never const이며 절대 변경할 수 없습니다. 어쨌든, 당신에 대한 나의 대답은 그렇습니다 .
궤도에서 가벼움 레이스

4
이것이 효과가있을 수 있지만, 위의 주어진 C64의 우수하고 포괄적 인 답변이 제공하는 더 미묘하고 고려 된 접근법과 비교할 때 이것은 조언이 좋지 않다고 생각합니다. 가장 일반적인 분모로 줄이는 것은 C ++의 목적이 아닙니다.
Jack Aidley

6
이 언어의 진화 제안이 "가난한"대답에 동의 : open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3853.htm
루크 Hermitte에게

1

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 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.