왜 배열 인덱스 대신 반복자를 사용합니까?


239

다음 두 줄의 코드를 사용하십시오.

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

이:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

나는 두 번째 방법이 선호된다고 들었습니다. 왜 이것이 정확히입니까?


72
두 번째 방법은로 변경 some_iterator++하는 것 ++some_iterator입니다. 증가 후 불필요한 임시 반복자가 작성됩니다.
Jason

6
또한 end()선언 조항을 가져와야 합니다.
궤도에서 가벼운 레이스

5
@Tomalak : 비효율적 인 C ++ 구현을 사용하는 사람 vector::end은 루프에서 들어 올렸는지 여부보다 걱정 할만한 문제가있을 수 있습니다. 개인적으로 나는 명확성을 선호합니다-그것이 find종결 조건에서 전화 라면 걱정할 것입니다.
Steve Jessop

13
@ Tomalak : 그 코드는 조잡하지 않습니다 (글쎄, 포스트 증가) 어쩌면 C ++ 반복자가 간결성을 허용하는 한 간결하고 명확합니다. 더 많은 변수를 추가하면 조기 최적화를 위해인지 노력이 추가됩니다. 부주의하다.
Steve Jessop

7
@ Tomalak : 병목 현상이 없으면 조기입니다. 두 번째 포인트는 정확한 비교가 사이 아니기 때문에, 나에게 터무니없는 것 it != vec.end()it != end그 사이의, (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)그리고 (vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it). 나는 문자를 세지 않아도됩니다. 반드시 다른 것을 선호하지만 다른 사람들이 선호하는 의견에 동의하지 않는 것은 "느슨 함"이 아니며 변수가 적고 코드를 읽는 동안 생각하는 것이 적은 간단한 코드를 선호합니다.
Steve Jessop

답변:


210

첫 번째 형식은 vector.size ()가 빠른 작업 인 경우에만 효율적입니다. 예를 들어 벡터에는 적용되지만 목록에는 적용되지 않습니다. 또한 루프 본문 내에서 무엇을 할 계획입니까? 다음과 같이 요소에 액세스하려는 경우

T elem = some_vector[i];

그런 다음 컨테이너가 operator[](std::size_t)정의 했다고 가정합니다 . 다시 말하지만 이것은 벡터에 해당하지만 다른 컨테이너에는 해당되지 않습니다.

반복자를 사용하면 컨테이너 독립성에 더 가까워집니다 . 랜덤 액세스 기능이나 빠른 size()작업 에 대해 가정하지 않고 컨테이너에 반복자 기능 만 있다고 가정 합니다.

표준 알고리즘을 사용하여 코드를 더욱 향상시킬 수 있습니다. 당신이 달성하려고하는 무엇에 따라 사용하도록 선택할 수 std::for_each(), std::transform()등등. 명시적인 루프가 아닌 표준 알고리즘을 사용하면 휠을 다시 발명하지 않아도됩니다. 올바른 알고리즘을 선택하면 코드가 더 효율적이고 정확하고 재사용 가능합니다.


8
또한 반복자가 페일-패스트 (fast-fast)와 같은 작업을 수행 할 수 있다는 점을 잊어 버렸습니다. 따라서 액세스중인 구조에 대한 동시 수정이 있으면 이에 대해 알게됩니다. 정수로만 할 수는 없습니다.
Marcin

4
이것은 나를 혼란스럽게한다. "예를 들어 벡터에는 해당하지만리스트에는 해당되지 않는다." 왜? 두뇌가있는 사람은 누구나 size_t변수를 추적하여 추적 size()합니다.
GManNickG

19
@GMan-거의 모든 구현에서 size ()는 벡터만큼이나 목록에 빠릅니다. 표준의 다음 버전에서는 이것이 사실이어야합니다. 실제 문제는 위치별로 후퇴 속도가 느리다는 것입니다.
Daniel Earwicker

8
@GMan : 목록 크기를 저장하려면 목록 슬라이싱 및 스 플라이 싱이 O (1) 대신 O (n)이어야합니다.

5
C ++ 0x에서 size()멤버 함수 는를 포함하여이를 지원하는 모든 컨테이너에 대해 일정한 시간 복잡성을 가져야 std::list합니다.
제임스 맥닐리 스

54

그것은 현대적인 C ++ indoctrination 프로세스의 일부입니다. 반복자는 대부분의 컨테이너를 반복 할 수있는 유일한 방법이므로 벡터와 함께 사용하여 올바른 사고 방식을 취할 수 있습니다. 진심으로, 그것이 내가하는 유일한 이유입니다. 벡터를 다른 종류의 컨테이너로 교체 한 적이 없다고 생각합니다.


와우, 이것은 여전히 ​​3 주 후에도 하향 조정되고 있습니다. 나는 뺨에 약간의 혀가되는 것이 아니라고 생각합니다.

배열 인덱스가 더 읽기 쉽다고 생각합니다. 다른 언어에서 사용되는 구문과 구식 C 배열에 사용되는 구문과 일치합니다. 덜 장황하다. 컴파일러가 훌륭하고 효율성이 중요한 경우 효율성이 떨어질 것입니다.

그럼에도 불구하고 여전히 벡터와 함께 반복자를 자주 사용합니다. 반복자가 중요한 개념이라고 생각하므로 가능하면이를 홍보합니다.


1
C ++ 반복자도 개념적으로 끔찍합니다. 벡터의 경우 끝 포인터가 끝 +1 (!)이기 때문에 방금 잡혔습니다. 스트림의 경우 이터레이터 모델은 존재하지 않는 가상의 토큰입니다. 연결된 목록도 마찬가지입니다. 패러다임은 배열에만 적합하며 그다지 중요하지 않습니다. 왜 하나가 아닌 두 개의 반복자 객체가 필요합니까?
Tuntable

5
@ aberglas 그들은 전혀 부서지지 않았으며, 당신은 그들에게 익숙하지 않으므로, 당신이 필요없는 경우에도 그것들을 사용하는 것을 옹호합니다! 반 개방 범위는 일반적인 개념이며 직접 액세스 할 수없는 센티넬은 프로그래밍 그 자체만큼 오래되었습니다.
Mark Ransom

4
스트림 반복자를 살펴보고 패턴에 맞추기 위해 ==가 어떻게 변질되었는지 생각한 다음 반복자가 손상되지 않았다고 알려주세요! 또는 링크 된 목록의 경우. 배열의 경우에도 끝을 지나쳐 하나를 지정해야한다는 것은 깨진 C 스타일 아이디어입니다. Java 또는 C # 또는 다른 언어의 반복자와 같아야하며 두 개의 오브젝트 대신 하나의 반복자가 필요하고 간단한 최종 테스트가 필요합니다.
Tuntable

53

코드를 some_vector 목록의 특정 구현에 연결하지 않기 때문입니다. 배열 인덱스를 사용하는 경우 배열 형식이어야합니다. 반복자를 사용하면 모든 목록 구현에서 해당 코드를 사용할 수 있습니다.


23
std :: list 인터페이스는 의도적으로 O (n)이므로 operator [] (size_t n)를 제공하지 않습니다.
MSalters

33

some_vector가 링크 된 목록으로 구현되었다고 상상해보십시오. 그런 다음 i 번째 장소에서 항목을 요청하려면 노드 목록을 탐색하기 위해 i 조작을 수행해야합니다. 이제 일반적으로 말해서 반복자를 사용하면 가능한 한 효율적으로 최선을 다할 것입니다 (연결 된 목록의 경우 현재 노드에 대한 포인터를 유지하고 각 반복에서 진행합니다. 단일 작업).

따라서 두 가지를 제공합니다.

  • 사용 추상화 : 일부 요소 만 반복하고 싶을뿐
  • 공연

1
"현재 노드에 대한 포인터를 유지하고 그것을 향상시킬 것입니다." 그것들은 개념적으로 포인터의 상위 집합입니다. 포인터를 캐시 할 수있을 때 왜 어떤 요소의 오프셋을 반복해서 계산합니까? 글쎄, 그것은 반복자가하는 일입니다.
underscore_d

27

나는 악마가 여기에서 옹호하고 반복자를 추천하지 않을 것입니다. 그 주된 이유는 데스크탑 응용 프로그램 개발에서 게임 개발에 이르기까지 내가 작업 한 모든 소스 코드가 반복자를 사용할 필요도 없었기 때문입니다. 항상 필요하지 않았으며 두 번째로 숨겨진 가정과 코드 혼란 및 디버깅 악몽은 속도가 필요한 응용 프로그램에서 사용하지 않는 주요 예입니다.

유지 관리 측면에서도 엉망입니다. 그것들 때문이 아니라 장면 뒤에서 일어나는 모든 앨리어싱 때문입니다. 표준과 완전히 다른 자체 가상 벡터 또는 배열 목록을 구현하지 않았다는 것을 어떻게 알 수 있습니까? 현재 런타임 중에 현재 어떤 유형인지 알고 있습니까? 모든 소스 코드를 확인할 시간이 없었던 연산자를 오버로드 했습니까? 심지어 사용중인 STL 버전을 알고 있습니까?

반복자와 관련하여 발생하는 다음 문제는 누출 추상화이지만, 이에 대해 자세히 설명하는 수많은 웹 사이트가 있습니다.

죄송합니다. 반복기의 요점을 아직 보지 못했습니다. 그들이 목록이나 벡터를 추상화하지 않는다면, 실제로 당신이 처리하지 않는 경우 어떤 벡터 또는 목록을 처리 해야하는지 알고 있어야합니다.


23

반복하는 동안 벡터에 항목을 추가 / 제거하려는 경우 반복자를 사용할 수 있습니다.

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

인덱스를 사용하는 경우 배열에서 항목을 위 / 아래로 섞어 삽입 및 삭제를 처리해야합니다.


3
컨테이너의 중간에 요소를 삽입하려면 벡터가 시작하기에 좋은 컨테이너 선택이 아닐 수 있습니다. 물론 이터레이터가 왜 멋진 지 다시 돌아 왔습니다. 목록으로 전환하는 것은 쉽지 않습니다.
wilhelmtell

그러나 모든 요소를 ​​반복하는 것은에 std::list비해 std::vector링크 목록을 사용하는 것이 좋습니다 std::vector. 43 페이지를 참조하십시오 : ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf 내 경험상, 모든 것을 검색하고 임의의 위치에서 요소를 제거하더라도 std::vectora보다 빠릅니다 std::list.
David Stone

인덱스는 안정적이므로 삽입 및 삭제에 어떤 추가 셔플 링이 필요한지 알 수 없습니다.
musiphil

... 그리고 여기에 사용해야하는 링크 된 목록이 있으면 루프 문은 for (node = list->head; node != NULL; node = node->next)처음 두 줄의 코드 (선언 및 루프 헤드)보다 짧습니다. 따라서 반복자를 사용하는 것과 사용하지 않는 것 사이의 간결성에는 근본적인 차이가 거의 없다고 다시 말하지만 선언, 반복, 종료 확인 for을 사용하더라도 명령문 의 세 부분을 만족시킵니다 while.
엔지니어

16

우려의 분리

반복 코드를 루프의 '핵심'관심사와 분리하는 것이 좋습니다. 거의 디자인 결정입니다.

실제로 인덱스로 반복하면 컨테이너 구현에 연결됩니다. 컨테이너에 시작 및 종료 반복자를 요청하면 다른 컨테이너 유형에 루프 코드를 사용할 수 있습니다.

또한, std::for_each당신 은 컬렉션 의 내부에 대해 뭔가 를 묻지 않고 무엇 을해야하는지 이야기 합니다.

0x 표준은 클로저를 도입 하여이 접근법을 훨씬 더 쉽게 사용할 수있게합니다-Ruby의 표현력을 살펴보십시오 [1..6].each { |i| print i; }...

공연

그러나 아마도 너무 많은 문제는 for_each접근 방식을 사용하여 반복을 병렬화 할 수 있다는 것입니다 . 인텔 스레딩 블록 은 코드 블록을 시스템의 프로세서 수에 분산시킬 수 있습니다!

참고 : algorithms라이브러리를 발견 한 후 특히 foreach2 ~ 3 개월 동안 작은 개발자 도우미를 작성하여 동료 개발자를 미치게 만들었습니다. 이 시간 이후, 나는 실용적인 접근법으로 돌아갔습니다. 작은 루프 바디는 foreach더 이상 가치가 없습니다. :)

반복자에 대한 참조는 "Extended STL" 책 입니다.

GoF는 Iterator 패턴의 끝 부분에 작은 단락이 있으며이 반복 브랜드에 대해 이야기합니다. 이를 '내부 반복자'라고합니다. 여기도 살펴보십시오 .


15

더 객체 지향적이기 때문입니다. 색인으로 반복하는 경우 다음을 가정합니다.

a) 해당 객체의 순서
b) 해당 객체를 인덱스로 얻을 수 있음
c) 인덱스 증분이 모든 항목에 부딪 힐 것
d) 해당 인덱스가 0에서 시작하는 것

반복자를 사용하면 기본 구현이 무엇인지 모른 채 "모든 것을 제공하여 작업 할 수 있습니다"라고 말합니다. (Java에는 색인을 통해 액세스 할 수없는 콜렉션이 있습니다)

또한 반복자를 사용하면 배열 범위를 벗어날 염려가 없습니다.


2
"객체 지향"이 올바른 용어라고 생각하지 않습니다. 반복자는 디자인에서 "객체 지향"이 아닙니다. 클래스에서 알고리즘을 분리하도록 장려하기 때문에 객체 지향 프로그래밍보다 기능 프로그래밍을 촉진합니다.
wilhelmtell

또한 반복자는 범위를 벗어나지 않도록 도와줍니다. 표준 알고리즘은 있지만 반복자만으로는 그렇지 않습니다.
wilhelmtell

충분히 @wilhelmtell, 나는 분명히 Java 중심의 관점에서 이것을 생각하고 있습니다.
cynicalman

1
그리고 컬렉션의 작업과 컬렉션의 구현을 분리하기 때문에 OO를 홍보한다고 생각합니다. 객체 컬렉션은 반드시 어떤 알고리즘을 사용해야하는지 알 필요는 없습니다.
cynicalman

실제로 반복자를 확인한 STL 버전이 있습니다. 즉, 반복자와 함께 무언가를 시도하면 범위를 벗어난 예외가 발생합니다.
Daemin

15

반복자에 대한 또 다른 좋은 점은 const-preference를 더 잘 표현하고 강제 할 수 있다는 것입니다. 이 예제는 루프 도중에 벡터를 변경하지 않도록합니다.


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}

이것은 합리적으로 보이지만 그래도 그 이유가 의심 스럽다 const_iterator. 루프에서 벡터를 변경하면 이유가 있으며 변경이 사고가 아닌 시간의 99.9 % 동안, 나머지는 코드 작성자의 코드와 같은 버그 일뿐입니다. 수정해야합니다. Java 및 다른 많은 언어에는 const 객체가 전혀 없기 때문에 해당 언어의 사용자는 해당 언어에서 const 지원이없는 문제가 발생하지 않습니다.
neevek

2
@neevek 이것이 이유가 아닌 경우 const_iterator지구상에서 그 이유는 무엇입니까?
underscore_d

@underscore_d, 나도 궁금합니다. 나는 이것에 대해 전문가가 아니며, 대답이 나에게 설득력이 없다는 것입니다.
neevek

15

다른 모든 훌륭한 답변을 제외하고는 int벡터에 충분하지 않을 수 있습니다. 대신 인덱싱을 사용하려면 size_type컨테이너에 대해를 사용하십시오 .

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}

1
@Pat Notz, 그것은 매우 좋은 지적입니다. STL 기반 Windows 응용 프로그램을 x64로 이식하는 과정에서 size_t를 int에 할당하여 잘릴 수있는 수백 가지 경고를 처리해야했습니다.
bk1e

1
크기 유형이 부호가없고 int 부호가 있다는 사실은 말할 것도없고, 비교 int i하기 위해 직관적이지 않은 버그 숨김 변환 이 myvector.size()있습니다.
Adrian McCarthy

12

아마 당신도 전화 할 수 있다고 지적해야합니다

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);


7

STL 이터레이터는 주로 존재하므로 sort와 같은 STL 알고리즘은 컨테이너 독립적 일 수 있습니다.

벡터의 모든 항목을 반복하려면 색인 루프 스타일을 사용하십시오.

대부분의 사람은 타이핑이 적고 구문 분석하기가 더 쉽습니다. C ++에 템플릿 매직을 사용하지 않고 간단한 foreach 루프가 있다면 좋을 것입니다.

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

5

벡터에 큰 차이가 있다고 생각하지 않습니다. 나는 더 읽기 쉽다고 생각하기 때문에 색인을 직접 사용하는 것을 선호하며 필요한 경우 6 항목 앞으로 이동하거나 뒤로 이동하는 것과 같이 임의 액세스를 수행 할 수 있습니다.

또한 루프 내부의 항목을 다음과 같이 참조하여 장소 주위에 대괄호가 많지 않습니다.

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

반복자를 사용하면 나중에 벡터를 목록으로 바꿔야 할 수도 있고 STL 괴물에게 더 세련된 것처럼 보이지만 다른 이유는 생각할 수 없다면 좋을 수 있습니다.


대부분의 알고리즘은 컨테이너의 각 요소에서 순차적으로 한 번 작동합니다. 물론 특정 순서 또는 방식으로 컬렉션을 탐색하려는 예외가 있지만이 경우 STL과 통합되고 반복자와 작동하는 알고리즘을 열심히 작성하려고합니다.
wilhelmtell

이렇게하면 재사용을 장려하고 나중에 오류를 피할 수 있습니다. 그런 다음 반복자를 사용하여 다른 표준 알고리즘과 마찬가지로 해당 알고리즘을 호출합니다.
wilhelmtell

1
advance ()가 필요하지 않습니다. 반복자는 인덱스와 같은 + = 및-= 연산자를 갖습니다 (벡터 및 벡터와 유사한 컨테이너의 경우).
MSalters

I prefer to use an index myself as I consider it to be more readable일부 상황에서만; 다른 경우에는 지수가 매우 지저분 해집니다. and you can do random access이는 인덱스의 고유 한 기능이 아닙니다 : en.cppreference.com/w/cpp/concept/RandomAccessIterator
underscore_d

3

두 번째 양식은 더 정확하게하는 일을 나타냅니다. 귀하의 예에서, 당신은 i의 가치에 대해 신경 쓰지 않습니다. 정말로-당신이 원하는 것은 반복기의 다음 요소입니다.


3

이 답변의 주제에 대해 조금 더 배운 후에, 나는 그것이 지나치게 단순화되었음을 깨달았습니다. 이 루프의 차이점 :

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

그리고이 루프 :

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

상당히 최소한입니다. 사실,이 방법으로 루프를 수행하는 구문은 나에게 점점 커지고 있습니다.

while (it != end){
    //do stuff
    ++it;
}

반복자는 상당히 강력한 선언적 기능을 잠금 해제하고 STL 알고리즘 라이브러리와 결합하면 배열 인덱스 관리 범위를 벗어나는 멋진 작업을 수행 할 수 있습니다.


진실은 모든 반복자가 최종 예만큼 컴팩트 한 경우 즉시 문제가되지 않는다는 것입니다. 물론, 이것은 실제로 for (Iter it = {0}; it != end; ++it) {...}선언 과 동일합니다. 따라서 간결함은 두 번째 예와 크게 다르지 않습니다. 여전히 +1입니다.
엔지니어

3

인덱싱에는 추가 mul작업이 필요합니다 . 예를 들어, for vector<int> v컴파일러는 v[i]로 변환 합니다 &v + sizeof(int) * i.


대부분의 경우 반복자와 관련하여 중요한 단점은 아니지만 알아 두는 것이 좋습니다.
nobar

3
격리 된 단일 요소 액세스의 경우. 그러나 OP와 마찬가지로 루프에 대해 이야기하고 있다면이 답변이 가상의 최적화되지 않은 컴파일러를 기반으로하고 있다고 확신합니다. 반쯤 괜찮은 사람은 sizeof매번 전체 오프셋 계산을 다시 수행하는 대신 캐시를 캐시하고 반복마다 한 번만 추가 할 수있는 충분한 기회와 가능성을 갖게 됩니다.
underscore_d

2

반복하는 동안 처리 할 항목 수를 알 필요가 없습니다. 항목이 필요하고 반복자가 그러한 일을 매우 잘 수행합니다.


2

아직 인덱스의 장점 중 하나는 인접 컨테이너에 추가 할 때 인덱스가 유효하지 않다는 것 std::vector입니다. 따라서 반복하는 동안 컨테이너에 항목을 추가 할 수 있습니다.

이터레이터로도 가능하지만을 호출해야 reserve()하므로 추가 할 항목 수를 알아야합니다.


1

몇 가지 좋은 점이 이미 있습니다. 몇 가지 추가 의견이 있습니다.

  1. 우리가 C ++ 표준 라이브러리에 대해 이야기하고 있다고 가정하면, "벡터"는 C- 배열 (랜덤 액세스, 콘티 구스 메모리 레이아웃 등)을 보장하는 랜덤 액세스 컨테이너를 의미합니다. 'some_container'라고 말하면 위의 답변 중 많은 부분이 더 정확했을 것입니다 (컨테이너 독립성 등).

  2. 컴파일러 최적화에 대한 종속성을 제거하기 위해 인덱스 코드의 루프에서 some_vector.size ()를 다음과 같이 이동할 수 있습니다.

    const size_t numElems = some_vector.size ();
    에 대한 (size_t i = 0; i 
  3. 항상 증분 반복기를 미리 늘리고 증분 후를 예외적 인 경우로 취급합니다.

for (some_iterator = some_vector.begin (); some_iterator! = some_vector.end (); ++ some_iterator) {// 할 일}

따라서 std::vector<>컨테이너 와 같이 가정하고 색인 을 생성 할 수 있으므로 컨테이너를 순차적으로 통과하는 것을 선호하는 이유는 없습니다. 이전 또는 최신 elemnent 색인을 자주 참조해야하는 경우 색인화 된 버전이 더 적합합니다.

일반적으로 알고리즘은 알고리즘을 사용하고 반복기 유형을 변경하여 동작을 제어하고 암시 적으로 문서화 할 수 있기 때문에 반복자를 사용하는 것이 좋습니다. 반복자 대신 배열 위치를 사용할 수 있지만 구문상의 차이가 나타납니다.


1

나는 foreach 문을 싫어하는 것과 같은 이유로 반복자를 사용하지 않습니다. 내부 루프가 여러 개인 경우 모든 로컬 값과 반복자 이름을 기억하지 않고도 전역 / 구성원 변수를 추적하기가 어렵습니다. 내가 유용하다고 생각하는 것은 다른 경우에 두 개의 인덱스 세트를 사용하는 것입니다.

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

예를 들어 "animation_matrices [i]"와 같은 것을 임의의 "anim_matrix"라는 이름의 반복자로 축약하고 싶지 않습니다.이 값이 어떤 배열에서 시작되었는지 명확하게 알 수 없기 때문입니다.


나는 이런 의미에서 지수가 어떻게 더 나은지 알지 못한다. 당신은 쉽게 반복자를 사용하고 단지 자신의 이름에 대한 규칙을 선택할 수있다 : it, jt, kt, 등 또는 심지어 그냥 계속 사용 i, j, k등, 그리고 당신은 내게 그런 다음, 반복자가 나타내는 정확히 무엇을 알고 무엇인가를해야하는 경우 for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()더 자세한 설명이 될 것입니다 처럼 지속적으로 색인을 생성하는 것보다 animation_matrices[animIndex][boneIndex].
underscore_d

와우, 내가 그 의견을 썼을 때의 느낌. 요즘에는 foreach와 c ++ 반복자를 많이 사용하지 않고 사용합니다. 나는 몇 년 동안 버그가 많은 코드를 사용하면 내성을
키울

하하, 실제로, 나는 이것이 이전의 나이를 실제로 보지 못했습니다! 내가 어쨌든 지난 번 생각하지 않은 것은 요즘 우리는 범위 기반 for루프를 가지고 있기 때문에 반복기 기반 방식으로이를보다 간결하게 수행한다는 것입니다.
underscore_d

1
  • 금속에 가까워지고 구현 세부 정보를 믿지 않으면 반복자를 사용하지 마십시오 .
  • 개발 중에 한 콜렉션 유형을 다른 콜렉션 유형으로 정기적으로 전환하는 경우 반복자를 사용하십시오 .
  • 여러 종류의 컬렉션을 반복하는 방법을 기억하기 어려운 경우 (여러 다른 외부 소스의 여러 유형이있을 수 있음) 반복자를 사용 하여 요소를 탐색하는 수단을 통합하십시오. 이것은 배열 목록으로 연결된 목록을 전환하는 데 적용됩니다.

실제로, 그게 전부입니다. 그것은 당신이 평균적으로 더 간결하게 얻을 것 같지 않으며, 간결성이 실제로 목표라면, 항상 매크로에 빠질 수 있습니다.


1

C ++ 11 기능에 액세스 할 수 있으면 다음과 같이 벡터 (또는 다른 컨테이너)를 반복 하기 위해 범위 기반 for루프 를 사용할 수 있습니다 .

for (auto &item : some_vector)
{
     //do stuff
}

이 루프의 장점은 item인덱스를 엉망으로 만들거나 반복자를 역 참조 할 때 실수를하지 않고 변수 를 통해 벡터 요소에 직접 액세스 할 수 있다는 것 입니다. 또한 자리 표시 auto자는 컨테이너 요소의 유형을 반복하지 않아도되므로 컨테이너 독립적 솔루션에 더 가깝습니다.

노트:

  • 루프에 요소 색인이 필요하고 operator[]컨테이너에 존재하고 충분히 빠르면 첫 번째 방법으로 이동하는 것이 좋습니다.
  • 범위 기반 for루프를 사용하여 컨테이너에 요소를 추가하거나 삭제할 수 없습니다. 그렇게하고 싶다면 Brian Matthews가 제공 한 솔루션 을 따르는 것이 좋습니다 .
  • 컨테이너의 요소를 변경하지 않으려면 const다음과 같이 키워드 를 사용해야합니다 for (auto const &item : some_vector) { ... }.

0

"무엇을해야하는지 CPU에게 알리는 것"(필수)보다 "좋아하는 라이브러리를 말하는 것"(기능적)입니다.

따라서 루프를 사용하는 대신 stl에있는 알고리즘을 배워야합니다.



0

필자의 많은 응용 프로그램에는 "디스플레이 썸네일 이미지"와 같은 것이 필요하기 때문에 항상 배열 인덱스를 사용합니다. 그래서 나는 다음과 같이 썼다 :

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}

0

두 가지 구현 모두 정확하지만 'for'루프를 선호합니다. 다른 컨테이너가 아닌 Vector를 사용하기로 결정 했으므로 인덱스를 사용하는 것이 가장 좋습니다. Vector와 반복자를 사용하면 객체를 연속 메모리 블록에 저장하면 액세스가 쉬워집니다.


2
"Vector와 함께 반복자를 사용하면 객체를 연속 메모리 블록에 저장하면 액세스가 쉬워집니다." [인용 필요]. 왜? 연속 컨테이너에 대한 반복기 증분을 간단한 추가로 구현할 수 없다고 생각하십니까?
underscore_d

0

컨테이너의 인덱싱에 대한 일반적인 개념으로 반복자를 좋아하는 이유를 설명하는 답변이 없습니다. 반복자를 사용한 대부분의 경험은 실제로 C ++이 아니라 Python과 같은 고급 프로그래밍 언어에서 온 것입니다.

반복자 인터페이스는 기능 소비자에게 더 적은 요구 사항을 부과하므로 소비자는 더 많은 기능을 수행 할 수 있습니다.

당신이 필요로하는 모든이 전달-반복 처리를 할 수있는 경우, 개발자는 색인 컨테이너를 사용에 제한되지 않는다 - 그들이 구현하는 어떤 클래스를 사용할 수 있습니다 operator++(T&), operator*(T)하고 operator!=(const &T, const &T).

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

알고리즘은 벡터를 반복하여 필요한 경우에 작동하지만 반드시 예상하지 않는 응용 프로그램에도 유용 할 수 있습니다.

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

이터레이터와 비슷한 것을 수행하는 대괄호 연산자를 구현하려고 시도하는 것이 고려되는 반면, 이터레이터 구현은 비교적 간단합니다. 대괄호 연산자는 클래스의 기능에 영향을 미치므로 임의의 지점으로 색인을 생성 할 수 있습니다. 이는 구현하기 어렵거나 비효율적 일 수 있습니다.

반복자들은 또한 장식에 적합하다 . 사람들은 자신의 생성자에서 반복자를 가져와 기능을 확장하는 반복자를 작성할 수 있습니다.

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

이러한 장난감은 평범 해 보이지만 반복자와 반복자 데코레이터를 사용하여 간단한 인터페이스로 강력한 작업을 수행하는 것을 상상하는 것은 어렵지 않습니다. 예를 들어 단일 결과에서 모델 객체를 생성하는 반복자로 데이터베이스 결과의 순방향 전용 반복자를 장식 . 이 패턴은 무한 세트의 메모리 효율적인 반복을 가능하게하며, 위에서 쓴 것과 같은 필터를 사용하여 잠재적으로 결과에 대한 게으른 평가가 가능합니다.

C ++ 템플릿의 강력한 기능 중 하나는 고정 길이 C 배열에 적용 할 때 간단하고 효율적인 포인터 산술로 분해되어 실제로 비용이없는 추상화로 만드는 반복자 인터페이스 입니다.

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