39-3-나.상수 반복자

안전한 참조를 위해 포인터나 레퍼런스에 상수성을 줄 수 있듯이 반복자도 상수성을 가질 수 있다. 비상수 반복자는 레퍼런스를 리턴하므로 *연산자와 함께 대입식의 좌변에 사용할 수 있지만 상수 반복자는 상수 레퍼런스를 리턴하므로 대입식의 좌변에 놓아 값을 변경할 수 없고 오로지 읽을 수만 있다. 상수 반복자와 비상수 반복자는 *연산자가 리턴하는 레퍼런스의 상수성이 다르다.

비상수 반복자는 각 컨테이너별로 iterator라는 타입으로 정의되는데 비해 상수 반복자는 const_iterator 타입으로 정의되어 있다. 읽기만 하는 상수 반복자가 필요하다면 이 타입으로 객체를 선언하면 된다. 단, 상수 반복자가 가리키는 대상이 상수이지 반복자 그 자체가 상수인 것은 아니므로 전후로 이동하여 다른 요소를 가리키는 것은 가능하다. const char *로 선언된 p 포인터로 문자를 바꿀 수 없지만 p 자체는 다른 문자로 이동 가능한 것과 마찬가지이다.

컨테이너의 begin, end 멤버 함수는 상수, 비상수 버전으로 중복 정의되어 있고 컨테이너가 상수일 때는 상수 반복자를 리턴한다. 비상수는 항상 상수에 대입할 수 있으므로 설사 비상수 컨테이너의 반복자라 하더라도 상수 반복자에 곧바로 대입할 수 있다. 따라서 다음 대입문은 문법적으로 합당하다.

 

vector<int> vi;

vector<int>::const_iterator cit=vi.begin();

 

vi가 비상수 컨테이너이므로 begin멤버 함수가 비상수 반복자를 리턴하지만 상수 반복자로 선언된 cit에 대입 가능하다. 왜냐하면 읽고 쓰기가 가능한 반복자를 받아 읽기 기능만 사용할 것이므로 전혀 위험하지 않기 때문이다. 그러나 반대로 상수 컨테이너의 반복자를 비상수 반복자에 대입하는 것은 안된다. 상수 컨테이너에 대해서는 반드시 상수 반복자만 사용해야 한다.

컨테이너란 만들고 나면 요소를 채워 넣어야 쓸 수 있으므로 사실 컨테이너를 상수로 생성할 일은 전혀 없다고 할 수 있다. 그러나 상수 컨테이너를 인수로 받는 함수가 존재할 수 있으며 전달받은 컨테이너가 이 함수 내에서는 임시적으로 상수성을 가지게 된다. 상수 컨테이너를 액세스할 때는 상수 반복자만 사용할 수 있다. 다음 예제의 vectorsum은 벡터의 합계를 구하는 함수이다.

 

: constiterator

#include <iostream>

#include <vector>

#include <algorithm>

using namespace std;

 

int vectorsum(const vector<int> &cvt)

{

     vector<int>::const_iterator cit;

     int sum=0;

 

     for (cit=cvt.begin();cit!=cvt.end();cit++) {

          sum+=*cit;

     }

     // *cit=1234;       // 에러

     return sum;

}

 

void main()

{

     int ari[]={80,98,75,60,100};

     vector<int> vi(&ari[0],&ari[5]);

 

     int sum;

     sum=vectorsum(vi);

     printf("총 합은 %d입니다.\n",sum);

}

 

요소들의 합계를 구하기 위해서는 벡터의 요소들을 순서대로 읽기만 하며 쓰는 동작은 전혀 필요없으므로 vectorsum 함수는 대상 벡터를 상수 레퍼런스로 전달받았다. 이 컨테이너의 요소를 순회하기 위해서는 상수 반복자만 사용할 수 있다. cit를 비상수 반복자로 선언하면 에러로 처리된다.

루프 내부에서 *cit로 벡터의 요소를 순서대로 읽어 sum에 누적하는데 읽는 것은 얼마든지 가능하다. 루프의 증감문에서는 cit++로 반복자를 다음 위치로 이동시키는데 반복자 자체는 상수가 아니므로 다른 위치로 이동할 수 있다. 그러나 *cit에 어떤 값을 대입하면 에러로 처리되는데 cit가 리턴하는 레퍼런스가 상수이기 때문이다.

상수 컨테이너에 대해서는 상수 함수만 호출할 수 있다. insert, erase 멤버처럼 컨테이너에 요소를 삽입, 삭제하는 함수는 컨테이너를 변경시키므로 상수 컨테이너로는 호출할 수 없다. 일반 알고리즘 함수들은 본체에서 어떤 동작을 하는가에 따라 상수 컨테이너 사용 가능성이 달라지는데 읽기만 하는 함수는 호출할 수 있지만 컨테이너를 변경하는 함수는 쓸 수 없다. 예제의 끝에 다음 두 줄을 추가해 보자.

 

find(cvt.begin(),cvt.end(),60);

sort(cvt.begin(),cvt.end());

 

cvt의 begin, end가 상수 반복자를 리턴하므로 두 함수는 상수 반복자를 받아들이는 버전이 구체화될 것이다. 이 함수들이 전달받는 반복자는 값을 변경하는 기능을 제공하지 않는다. find 함수는 값을 검색하기 위해 읽기만 하므로 상수 반복자와 함께 사용되어도 아무 문제가 없다. 그러나 sort는 값을 교환하기 위해 반복자에 값을 쓰는데 상수 반복자가 이 기능을 제공하지 않으므로 컴파일 에러로 처리된다. 타입이 불일치하므로 잘못된 동작을 컴파일 중에 즉시 알아낼 수 있다.