정체불명의 모모

Effective STL - Chapter4 [ 반복자(Iterators) ] 본문

프로그래밍(c++)/Effective STL(C++)

Effective STL - Chapter4 [ 반복자(Iterators) ]

정체불명의 모모 2021. 10. 1. 17:33

이번 장은 '반복자'에 대해 알아보는 시간입니다.

총 4가지의 반복자에 대해 알아보져!

그리고 컨테이너와 그 컨테이너의 멤버 함수의 관계도 알아 볼겁니다.

그럼... Start!


const_iterator나 reverse_iterator, const_reverse_iterator도 좋지만 역시 쓸만한 것은 iterator이다.

위의 내용을 파악하기 전에 iterator 종류에 대해 알아 보자

  • iterator의 종류(T-> template)
    iterator T*
    const_iterator const T*
    reverse_iterator 역방향 T*
    const_reverse_iterator 역방향 const T*
  • Iterator에서의 const 키워드의 의미
    : STL 반복자(iterator)는 포인터를 본떠 만든 것이기 때문에, 기본적인 동작 원리가 T* 포인터와 진짜 흡사 합니다.
      Iterator를 const로 선언하는 일은 포인터 자체를 상수로 선언하는 것과 같습니다.
        그렇기에 Iterator가 가리키고 있는 데이터의 값을 변경하는 것을 막고자 할때 사용합니다.
      즉, 포인터가 가리키는 값을, 포인터를 통해서 변경하는 걸 막고자 하는 제약을 거는 겁니다.

Iterator에서 값을 참조 하되 , 그 값을 변경하는 것은 안된다.


  Q. 왜 저자는 iterator를 사용하라는 걸까?

  • 이유 1 : 자료구조들의 매개변수 타입 때문에 
    ex) vector<T>에 제공되는 함수들
    iterator insert(interator position , const T& x);
    iterator erase(iterator position);
    iterator erase(iterator rangeBegin , iterator rangeEnd);​
    : 위 함수들은 받아들이는 매개 변수 타입으로 오직 iterator를 요구한다는 점입니다.
     
  • 이유 2 : iterator는 다른 종류의 iterator로 변환이 가능하다는 것 입니다.
    아래의 그림에서 화살표만 그려진 관계는 컴파일러의 추론에 의한(implicit) 변환이 가능하다는 뜻입니다.
     중요한 내용은 const_iterator를 iterator로 바꾸거나 const_resverse_iterator를 reverse_iterator로  
     바꿀 수 없게 되어 있습니다.

     이 뜻은 const_iterator 과 const_reverse_iterator만 가지고는 STL 표준 컨테이너의 어떤 멤버 함수를 
     사용하는데 있어 문제가 많다는 것 입니다.

STL의 네가지 반복자 사이의 상호 변환 관계를 나타내는 그림

반복자를 마구잡이로 섞어서 쓰지 맙시다.


const_iterator를 iterator로 바꾸는 데에는 distance 와 advance를 사용하자

ㅁ const_iterator  iterator 로 바꾸려는 시도( 캐스팅 )
 -  const_cast<T>로 타입 변경 시도
    에러 나는 이유 : 컨테이너 타입에서는 const_iterator와 iterator는 서로 다른 클래스 입니다.
    전혀 다른 클래스로 캐스팅 하려고 해서 컴파일러가 const_cast를 거부하는 것 입니다.

 

" 그럼, const_iterator를 interator로 안전하게 변경 할 수 없는 걸 까요? "


std::advance(iterator ,distance)  

 : iterator를 해당 값 크기 만큼 이동시킨다.

 

std::distance(iterator_begin , iterator_end)

 : iterator 사이의 크기를 반환 한다.

 

advance 와 distance를 이용한 안전한 이식
: const_iterator -> iterator 로 이식

- 방법 설명

1. 컨테이너의 첫 요소를 가리키는 iterator를 하나 생성합니다.
2. 첫 요소를 가르키는 iterator를 const_iterator가 있는 곳 까지 이동합니다.

3. 그러기 위해서 iterator 와 const_iterator 사이의 거리(크기)를 쟤 줍니다.

    (위에서 2~3 단계가 : advance(i , std::distacne<citer>(i,ci)) 입니다.)

 

결과 적으로 : iterator 와 const_iterator가 같은 컨테이너를 가리키고 있는 한, 
advance(iterator, distance(iterator, const_iterator)) 표현식이 실행되었을 때 컨테이너 내의 같은 위치에

있게 됩니다.

 

   ㅁ 효율성

    - 임의 접근 반복자의 경우(vector, string , deque) : 상수 시간 소요

    - 양방향 반복자의 경우(hash 등) : 선형 시간이 소요 됨

    - const_itertator -> iterator 바꿀 경우 : 선형의 시간 소요됨

  

   ㅁ 문제점

     : const_iterator에 대한 컨테이너가 없을 때는 전혀 소용이 없다.

 

 그렇기에 저자는 const_iterator 와 같은 다른 종류의 iterator 가 아닌 ,

기본형 iterator를 쓰는 것이 나중에 유지보수 시 편리하다는 

것을 말하고 있다.


 

reverse_iterator에 대응되는 기점 반복자(base iterator)를 사용하는 방법을 정확하게 이해하자

 : reverse iteartor에서 base 멤버 함수를 호출하면 이에 "대응되는(corresponding)" iterator를 얻어낼 수 있다고 합니다.

int main()
{
	vector<int> v;
    r.reserve(5);
    
    for(int i = 1; i <= 5; ++i)
    {
    	v.push_back(i);
    }
    
    // ri가 3을 가리키게 합니다.
    vector<int>::reverse_iterator ri = find(v.rbegin() , v.rend(), 3);
    
    // i가 ri의 기점과 같게 합니다.
    vector<int>::iterator i(ri.base());
}

 

위 코드 그림 설명

reverse_iterator를 요소 삽입을 하기 위해 iterator로 바꾸는 방법!

 : base( ) 라는 멤버 함수 이용 하기

 

  • iterator로 insert

  • reverse_iterator.base()로 insert

" 둘다 같은 결과 값을 가져오는것을 알 수 있다. "

 


 reverse_iterator를 요소 삭제를 하기 위해 iterator로 바꾸는 방법!

 : base( ) 라는 멤버 함수 이용

 

- 안 좋은 방법(저자는 컴파일 되지 않을 거라 했지만, 현재 컴파일 되고 있음)

- 올바른 방법(반환 값을 수정하지 않는 방법)

- 이유 : C 와 C++의 규정에 의하면, 함수에서 반환된 포인터는 수정(modify) 할 수 없기 때문에, vector 나 string의

   iterator가 포인터로 구현된 STL 플랫폼에서 --ri.base( ) 라는 표현식은 컴파일 될 수가 없습니다.(하지만, 현재 되고 있다 ㅎㅎ)

 

  • iterator로 erase 의 결과 값

 

  • reverse_iterator.base()로 erase 결과 값

 

위의 insert 와 erase의 상황을 봤다 싶이,

reverse_iterator의 base 멤버 함수를 호출 한다고 해서

원하는 iterator의 값이 나오는 것이 아닙니다.

그러니 목적에 맞춰 잘 사용하면 될 것 같습니다.


문자 단위의 입력에는 istreambuf_iterator의 사용도 적절하다

Q. 아래 코드의 문제점은 무엇일까요??

ifstream inputFile("interestingData.txt");

string fileData((istream_iterator<char>(inputFile)),
istream_iterator<char>());

A. 답은 '공백 문자'를 string 객체에 복사하지 못한다는 것 입니다.

    istream_iterator는 실제 스트림 읽기를 수행할 때에 operator>> 함수를 사용하며, 이 연산자는 공백 문자를 건너 뛰기 때문입니다.

 

ㅁ 해결 방법

inputFile.unsetf(ios::skipws);	// inputFile이 공백 문자를 
                                // 건너 뛰지 못하도록 합니다.

 

하지만! istream_iterator은 수행 성능이 빠르지 않습니다.

operator>> 함수에서 많은 일을 하고 있기 때문입니다.

만약, 입력 스트림으로부터 문자를 뽑아내는 일만이 목적이라면 

istreambuf_iterator을 사용하는것을 효율이 좋습니다.

 

ㅁ istream_iterator 보다 istreambuf_iterator를 사용하기 좋은 경우

 : 입력 스트림으로 부터 문자를 뽑아내는 일만 하는 상황

 

ㅁ istream_iterator 과 istreambuf_iterator의 문자 읽는 방식

operator >> s.rdbuf( )->sgetc( )

 (istreambuf_iterator의 경우 스트림 자체의 버퍼를 직접 건드려서 읽음)

 

ㅁ 사용 방법

ifstream inputFile("interestingData.txt");

string fileData((istreambuf_iterator<char>(inputFile)),
		istreambuf_iterator<char>());

ㅁ 성능 차이

 istream_iterator < istreambuf_iterator

 

문자 단위의 비서식화 출력을 수행하는 스트림 반복자는 

서식 출력 스트림 반복자 보다 훨씬 빠릅니다.


참조 사이트 

https://nedy.tistory.com/30

 

[EC++]Iterator에서의 const 키워드

카테고리 EC++는 스콧 마이어스의 Effective C++ 책에 수록된 내용을 바탕으로 공부한 내용을 적은 게시글입니다. STL 반복자(Iterator)는 포인터를 본떠 만든 것이기 때문에, 기본적인 동작 원리가 T *

nedy.tistory.com

 

Comments