[Modern C++] 항목 13: iterator보다 const_iterator를 선호하라
카테고리: Cpp
이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 C++, 스콧 마이어스 저자, 류광 번역
가독성이 떨어지는 직역들을 수정하며 정리하였습니다. e.g. 연역 → 추론, 중복적재 → 오버로딩
📦 3. 현대적 C++에 적응하기
👉🏻 항목 13: iterator보다 const_iterator를 선호하라
🔍 const_iterator란?
const_iterator는const를 가리키는 포인터의 STL 버전이다. 가리키는 값을 수정할 수 없다.- “가능한 한 항상
const를 사용하라”는 원칙은 반복자에도 동일하게 적용된다.- 반복자가 가리키는 것을 수정할 필요가 없을 때에는 항상
const_iterator를 사용하는 것이 바람직하다.
- 반복자가 가리키는 것을 수정할 필요가 없을 때에는 항상
⚠️ C++98에서의 문제점
C++98에서는 const_iterator로 이 원칙을 지키는 것이 불편했다.
std::vector<int> values;
...
std::vector<int>::iterator it =
std::find(values.begin(), values.end(), 1983);
values.insert(it, 1998);
- 이 코드는
iterator가 가리키는 것을 전혀 수정하지 않으므로,const_iterator를 써야 마땅하다. - 그러나 C++98에서는 비
const컨테이너로부터const_iterator를 얻는 간단한 방법이 없었다.
// C++98 방식 (개념적으로는 맞지만 실제로는 불편함)
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); // 컴파일 안 될 수 있음
- C++98에서는 삽입/삭제 위치를
iterator로만 지정할 수 있었고,const_iterator는 허용되지 않았다. - 심지어
const_iterator를iterator로 캐스팅하는 이식성 있는 방법도 존재하지 않았다. - 결론: C++98에서
const_iterator는 너무 불편해서 대부분의 경우 그냥 쓰지 않는 것이 낫다는 분위기였다.
✅ C++11에서의 개선
C++11에서는 const_iterator를 얻기도 쉽고 사용하기도 쉬워졌다.
std::vector<int> values;
...
auto it =
std::find(values.cbegin(), values.cend(), 1983); // cbegin과 cend를 사용
values.insert(it, 1998);
- 컨테이너 멤버 함수
cbegin()과cend()는 비const컨테이너에서도const_iterator를 돌려준다. - STL 멤버 함수들(
insert,erase등)도 이제 실제로const_iterator를 사용한다. - 이것이 바로
const_iterator를 사용하는 실용적인 C++11 코드다.
📋 최대한 일반적인 코드 작성 (C++14)
일반성을 극대화한 라이브러리 코드를 작성할 때는, 멤버 함수 대신 비멤버 함수 버전을 사용하는 것이 좋다. 컨테이너뿐만 아니라 배열, 서드파티 라이브러리 등에도 동작하기 때문이다.
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에서는 비멤버
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을 호출하지 않는 것이 의아할 수 있지만, 이는 논리적이다.- 타입
C가std::vector<int>라면,container는const std::vector<int>&가 된다. - 이
const컨테이너에 비멤버begin을 호출하면const_iterator타입의 반복자가 반환된다.
- 타입
begin멤버 함수는 제공하지만cbegin멤버 함수는 제공하지 않는 컨테이너에도 동작하는 장점이 있다.- C가 내장 배열 타입일 때에도 작동한다.
🧐 정리
iterator보다const_iterator를 선호하라.- 최대한 일반적인 코드에서는
begin,end,cbegin,cend,rbegin등의 비멤버 버전을 멤버 함수보다 선호하라.
댓글남기기