Post List

2015년 4월 2일 목요일

item 13: 이젠 const_iterator를 써도 된다.

작가
Meyers, Scott
출판
O'ReillyMedia
발매
2014.12.12
평점








Effective Modern C++ - Scott Meyers
Item 13: Prefer const_iterators to iterators.

Item 13. 이젠 const_iterator를 써도 된다.

원래 제목대로 하지 않고 조금 바꿨습니다.

const_iterator는 STL의 pointers-to-const 개념입니다.
( 즉, 개체의 값을 수정할 수 없는 상태의 포인터입니다. )
당연히 iterator는 개체의 값을 수정할 수 있는 상태의 포인터이구요.
둘의 용도가 다르죠 ?
당연히 값을 바꿀 필요가 없다면 iterator보다는 const_iterator가 더 빠르니깐 사용하는게 좋습니다.

"함수 선언에서도 const를 사용할 수 있으면 무조건 써라"고, Effective C++에서 Scott 아저씨가 노래를 불렀지만,
현업에서 솔직히 const키워드를 보기 힘듭니다.

하지만 const_iterator를 보기 힘든건 이유가 조금 다를 수 있습니다.
(물론 대부분은 쓰기 귀찮아서 겠지만서두요.)

C++98에서는 const_iterator가 완벽하게 구현된게 아니라서, 제 역할을 못했습니다.
그래서 당연히 사용하지 않았구요.
C++11에서는 사용 방법도 편해졌으며, 이제는 제 구실을 할만큼 보완이되었습니다.

앞에 말한 내용들에 대해서 구체적인 예제 Code를 보면서 하나하나 살펴보겠습니다.

예제) std::string를 요소로 가지는 std::vector에서 ""Luna" 라는 값을 찾아서 바로 뒤에 "Star"라는 값을 삽입합니다.
만약 "Luna"가 없다면 맨 끝에 삽입이 될 것입니다.

1. C++98 iterator

std::vector<std::string> VEC;

std::vector<std::string>::iterator IT = std::find(VEC.begin(), VEC.end(), "Luna");
VEC.insert(IT, "Star");

위 Code에서 std::vector의 요소 값을 수정하는 Code는 없습니다.
그러니 iterator보다는 const_iterator를 사용하도록 수정해보겠습니다.

2. C++98 const_iterator

std::vector<std::string> VEC;

std::vector<std::string>::const_iterator IT =
        std::find(static_cast<std::vector<std::string>::const_iterator>(VEC.begin()),
                  static_cast<std::vector<std::string>::const_iterator>(VEC.end()),
                  "Luna");

VEC.insert(static_cast<std::vector<std::string>::iterator>(IT), "Star");

Code가 굉장히 아름다워 졌습니다.
암걸리기 딱좋은 Code가 되었네요.
이만큼 노력해서 고쳤으면 되어야 할텐데, 이건 컴파일도 안됩니다.

std::vector의 멤버 함수인 insert()는 iterator만 지원합니다.
근데 const_iterator에서 iterator로 캐스팅이 안됩니다.
static_cast로는 물론이고 문법파괴자라 불리는 reinterpret_cast로도 안됩니다.

그래서 C++98에서 const_iterator를 사용하지 않았습니다.
이제 C++11에서 iterator를 사용하는 예제를 보겠습니다.

3. C++11 iterator

std::vector<std::string> VEC;

auto IT = std::find(VEC.begin(),VEC.end(),"Luna");
VEC.insert(IT, "Star");

C++98과 바뀐거라고는 auto를 사용한 것 뿐인데, 훨씬 깔끔해 보입니다.
이제 C++11의 const_iterator버전을 살펴보겠습니다.

4. C++11 const_iterator

std::vector<std::string> VEC;

auto IT = std::find(VEC.cbegin(),VEC.cend(),"Luna");
VEC.insert(IT, "Star");

iterator 버전과의 차이점은 begin(), end() 대신 cbegin(),cend()를 사용한 것 뿐입니다.
insert()도 const_iterator를 지원해 줍니다.

이제 위 Code를 일반화한 template 함수로 만들어 보겠습니다.

template <typename C, typename V>
void InsertAt(C& CON, const V& FIND, const V& INSERT)
{
    auto IT = std::find(std::cbegin(CON), std::cend(CON), FIND);
    CON.insert(IT, INSERT);
}

하지만 위 함수는 C++14에서는 되지만, C++11에서는 안됩니다.
왜냐면 C++11에 비멤버 함수로 begin(), end()는 있지만,
cbegin(), cend(), rbegin(),rend(), crbegin(),crend()는 없습니다.
C++14에는 다 있습니다.

C++11에서 cbegin()을 직접 만들어서 사용을 하는 방법으로 적용이 가능합니다.

template<class C>
auto cbegin(const C& CON) ->declytype(std::begin(CON)) {  return std::begin(CON); }

C++11에서 비맴버 함수인 std::begin()은 const 컨테이너에 대해서는 const_iterator를 반환합니다.
위 함수에서 인자를 const로 받기 때문에 늘 const_iterator를 반환합니다.
STL의 컨테이너 뿐 아니라 일반 배열의 경우도 똑같이 적용됩니다.

Things to Remember

*  iterator보다는 const_iterator를 더 선호해서 사용하는게 좋습니다.
   (단 2개의 용도는 엄연히 다릅니다. 무조건 const_iterator를 쓰자는 말은 아닙니다.)

* 일반화된 Code를 만들땐 begin(), end(), cbegin() 등에 대해서 멤버 함수가 아닌 비멤버 함수를 사용하자.
  (그럼 STL이 아닌 일반 배열에 대해서도 똑같이 동작하게 됩니다.)

댓글 없음:

댓글 쓰기