ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Modern C++] const_iterator
    C++/Effective Modern C++ 2022. 4. 27. 13:39

    const_iterator는 문자 그대로 const한 iterator입니다.

    const를 사용하는게 가능하다면 const를 사용하는 것은 iterator에도 적용이 되어, 값을 수정할 필요가 없을 때에는 const_iterator를 쓰는 것이 바람직합니다.

    하지만, C++11 이전에는 이 const_iterator를 사용하는 것이 조금 난해했습니다.

    이번 글에서는 const_iterator에 대하여 다루어 보겠습니다.

     


     

    std::vector<int>에서 1이라는 값을 찾고 그 앞에 2를 삽입하는, 혹은 값이 존재하지 않을 경우 벡터의 끝에 2를 삽입하는 코드는 아래와 같이 작성할 수 있습니다.

    std::vector<int> vec;
    ...
    std::vector<int>::iterator it = 
        std::find(vec.begin(), vec.end(), 1);
    vec.insert(it, 2);

    위 코드의 경우 iterator을 수정하지 않으므로, const_iterator를 사용하는 것이 마땅합니다.

     

    아래 문단들에서 이어집니다.

     

    1. const_iterator : C++98

     

    더보기

    C++98에서는 const_iterator를 다음과 같이 사용할 수 있습니다.

    typedef std::vector<int>::iterator vec_iter;
    typedef std::vector<int>::const_iterator vec_citer;
    
    std::vector<int> vec;
    ...
    vec_citer cit = 
        std::find(
            static_cast<vec_citer>(vec.begin()),
            static_cast<vec_citer>(vec.end()),
            1);
    vec.insert(static_cast<vec_iter>(cit), 2);

     

    using대신 typedef를 사용한 것은, using은 C++11에 추가된 기능이기 때문입니다.

    iterator를 사용한 코드보다 조금 복잡해졌는데, 더 큰 문제점은 이것이 컴파일이 되지 않을 수 있다는 것 입니다.

     

    위 코드의 난해한, 어색한 부분들은 다음과 같습니다.

    • C++98에서는 non-const 컨테이너로부터 const_iterator를 얻는 간단한 방법이 존재하지 않았기 때문에, 억지로 캐스팅을 했습니다.
    • C++98에서는 insert및 erase의 위치를 지정할 때 const_iterator를 사용할 수 없었습니다.
    • C++98에는 const_iterator에서 iterator로의 이식성 있는 변환이 존재하지 않습니다. 따라서, 위 코드는 컴파일 되지 않을 수 있습니다.

    위와 같은 문제점들 때문에, C++98에서는 const_iterator를 사용하는 것이 실용적이지 않았습니다.

     

    2. const_iterator : C++11

     

    더보기

    위 문단의 코드를 C++11을 기준으로 수정하면 다음과 같이 사용할 수 있습니다.

    std::vector<int> vec;
    ...
    auto cit = 
        std::find(
            vec.cbegin(),
            vec.cend();,
            1);
    vec.insert(cit, 2);

    C++11부터 const_iterator를 얻는 것이 굉장히 간편해졌습니다.

    컨테이너의 iterator반환 함수 begin, end의 const한 버전인 cbegin, cend를 통해 const_iterator를 얻을 수 있습니다.

    또한 insert, erase등 위치 지정을 목적으로 iterator를 사용하는 멤버 함수들에 const_iterator를 사용할 수 있게 되었습니다.

     

     

    3. iterator와 비 멤버 함수 (Non member function)

     

    더보기

    begin, end와 같은 iterator를 반환하는 멤버 함수들은 비 멤버 함수로도 존재합니다.

    이런 iterator를 다루는 비 멤버 함수들은 일반적인 코드 (ex. 라이브러리)를 작성할 때 유용합니다.

    멤버 함수에 iterator가 없는 (ex. 내장 배열)자료구조에도 작동하는, 위 문단의 탐색 및 삽입 기능을 하나의 함수로 만들어보면 다음과 같이 할 수 있습니다.

    template<typename C, typename V>
    void find_and_insert(C& container,
    	const V& target_val,
    	const V& insert_val) {
        
    	auto cit = std::find(
    		cbegin(container),
    		cend(container),
    		target_val);
            
    	container.insert(cit, insert_val);
    }

    위 코드의 cbegin, cend함수는 const_iterator를 반환하는 비 멤버 함수입니다.

    하지만, const하거나 참조 형식의 iterator를 반환하는 cbegin, cend, rbegin, rend함수들은 C++11이 아닌 C++14에서 추가되었습니다.

     

    위 함수를 C++11에서 사용하기 위해 cbegin, cend를 직접 구현할 수 있는데, 다음과 같이 할 수 있습니다.

    template<typename C>
    auto cbegin(const C& container) -> decltype(std::begin(container)) {
        return std::begin(container);
    }

    begin을 이용해 cbegin을 구현했지만, begin이 참조하는 매개변수가 const하기 때문에 const_iterator가 반환됩니다.

     


     

    수정되지 않는, 혹은 수정되지 않아야 하는 변수들은 const로 선언하는 것이 좋습니다.

    하지만, 본문의 예제와 같이 C++98에서는 iterator에 대하여 위 사항을 지키는 것이 난해했습니다.

    이러한 문제들이 C++11, C++14로 발전하면서 해결되었으니 const_iterator를 사용할 수 있는 상황이라면 사용하는 것이 좋겠습니다.

     

    감사합니다.

    댓글

Designed by Tistory.