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 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
-
컨테이너가 “프록시 반복자” (예 :)를 사용하는 경우 다음을
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>
.)
따라서 일반 코드 에서 다음 지침을 제공 할 수 있습니다.
-
요소 를 관찰 하려면 다음을 사용하십시오.
for (const auto& elem : container)
-
적절한 요소 를 수정 하려면 다음을 사용하십시오.
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