728x90
반응형

C++98에서 const_iterator는 그리 실용적이지 않았다.

왜냐면, 비상수 컨테이너로부터 const_iterator를 얻는 방법도 까다로웠고, 삽입과 삭제 위치를 iterator로만 지정할 수 있었기 때문이다.

하지만 C++11에서는 C++98에서의 문제점들이 개선되었다. 따라서 C++11이상의 환경에서 프로그래밍을 한다면 가능한 iterator보다 const_itertaor를 사용하는 것이 좋다.

// 예를 들어 std::vector<int>에서 1983이라는 값이 처음 나오는 지점을 찾고 그 곳에 1998이라는 값을 삽입한다고 하자.
// 벡터에 1983이 하나도 없으면 1998을 벡터의 끝에 삽입해야 한다.

// C++98에서는 iterator를 사용하여 간단하게 구현할 수 있다.
std::vector<int> values;
...
std::vector<int>iterator it = std::find(values.begin(), values.end(), 1983);
values.insert(it, 1998);

// 하지만 const_iterator를 사용하면 여러가지 문제가 많이 생긴다.

typedef std::vector<int>::iterator IterT;
typedef std::vector<int>::const_iterator ConstIterT;

std::vector<int> values;
...
ConstIterT ci = std::find(static_cast<ConstIterT>(values.begin()), static_cast<ConstIterT>(values.end()),1983);
values.insert(static_cast<IterT>(ci), 1998); // 컴파일이 안 될 수 있음

// std::find에서 캐스팅을 사용한 이유는 C++98에서 비상수 컨테이너로부터 상수 반복자를 얻을 수 있는 방법이 없기 때문이다.
// insert에서 다시 비상수 반복자로 캐스팅을 한 이유는, 삽입(과 삭제) 함수가 오직 iterator만을 받아들이기 때문이다.
// 그런데 상수 반복자를 비상수 반복자로 이식성을 유지한 채 변환하는 방법 또한 존재하지 않는다(이는 C++11도 마찬가지이다).
// 즉 캐스팅이 안된다는 말이다.

// 이런 불편함 때문에 C++98에서는 const_iterator를 잘 사용하지 않았다.
 
//---
// 하지만 C++11에서는 비상수 컨테이너로부터 const_iterator를 얻을 수 있는 멤버 함수가 추가되었다.(cbegin과 cend)
// 그리고 삽입(과 삭제) 함수가 const_iterator를 받아들일 수 있도록 바뀌었다.

std::vector<int> values;
...

auto it = std::find(values.cbegin(), values.cend(), 1983); // cbegin, cend를 사용
values.insert(it, 1998);

 

그리고 일반적인 코드를 작성할 때는 begin, end, rbegin등의 멤버 함수를 갖지 않은 컨테이너(배열 등...)도 고려해야 하므로 이 멤버 함수들의 비멤버 버전을 사용하는 것이 좋다.

// 앞에서 본 검색 및 삽입 코드를 findAndInsert라는 하나의 템플릿으로 일반화한 것이다.

// container에서 targetVal의 첫 위치를 찾고, 그 위치에 insertVal을 삽입한다.
template <typename C, typename V>
void findAndInsert라는(C& container, const V& targetVal, const V& insertVal)
{
    using std::cbegin;
    using std::cend;
    
    auto it = std::find(cbegin(container), // 비멤버 cbegin
                        cend(container), // 비멤버 cend
                        targetVal);
    
    container.insert(it, insertVal);
}
 
// 이 템플릿은 C++14에서는 잘 작동하지만, 안타깝게도 C++11에서는 그렇지 않다.

// C++11 표준화 과정에서 비멤버 함수 begin과 end는 표준에 추가했지만, cbegin과 cend, rbegin, rend, crbegin, crend는 빼먹고 추가하지 않았기 때문이다.
// C++11에서 이런 비멤버 함수들을 사용하고 싶다면, 직접 구현해야 한다.

// 다음은 비멤버 cbegin의 한 구현이다. 
template <class C>
auto cbegin(const C& container)->decltype(std::begin(container))
{
    return std::begin(container);
}

// 비멤버 cbegin이 멤버 cbegin을 호출하지 않는다는 점이 의아할 수도 있지만 이는 논리적이다. 
// 이 cbegin 템플릿은 컨테이너 같은 자료구조를 대표하는 임의의 인수 타입 C를 받고, 해당 const 참조 매개변수 container를 통해서 그 자료구조에 접근한다.
// 만일 C가 통상적인 컨테이너 타입이면 container는 그 컨테이너의 const 버전에 대한 참조가 된다.
// 그러한 const 컨테이너에 대해 비멤버 begin 함수를 호출하면 const_iterator 타입의 반복자가 반환된다.
// 이런 구현 방식의 장점은, begin 멤버 함수를 제공하지만 cbegin 멤버 함수는 제공하지 않는 컨테이너에 대해서도 작동한다는 것이다.
// C++11은 내장 배열에 특화된 버전의 비멤버 begin을 제공하기 때문에, C가 내장 배열 타입일 때에도 작동한다.
728x90
반응형