컨테이너의 요소를 관찰 하는 것과 수정 하는 것의 구별을 시작합시다. 하는 .
요소 관찰
간단한 예를 보자.
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 by const
reference , 즉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)
프록시 반복자 사례에서도 잘 작동합니다.
요약
위의 논의는 다음 지침에 요약 될 수 있습니다.
요소 를 관찰 하려면 다음 구문을 사용하십시오.
for (const auto& elem : container) // capture by const reference
객체가 복사하기 에 저렴한 경우 (예 : int
s, double
s 등) 약간 단순화 된 형식을 사용할 수 있습니다.
for (auto elem : container) // capture by value
적절한 요소 를 수정 하려면 다음을 사용하십시오.
for (auto& elem : container) // capture by (non-const) reference
물론 루프 바디 내부에서 요소 의 로컬 복사본 을 만들어야하는 경우 값 ( 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>
.)
따라서 일반 코드 에서 다음 지침을 제공 할 수 있습니다.
요소 를 관찰 하려면 다음을 사용하십시오.
for (const auto& elem : container)
적절한 요소 를 수정 하려면 다음을 사용하십시오.
for (auto&& elem : container)