Effective C++/Effective STL
[Effective STL] 2. 컨테이너 독립성(Container-Independent)
김디트
2024. 10. 30. 11:29
728x90
https://delightlane.tistory.com/161항목 2. "컨테이너에 독립적인(container-independent) 코드"라는 환상을 조심하자
과한 일반화(generalization)의 적용
- 코드 작성 시 일반화를 고려하게 될텐데, 이를 과하게 적용시키려 하는 것은 문제이다.
- 컨테이너에 독립적인(container-independent) 코드를 작성하려는 욕심.
- 모든 컨테이너에 대해 사용할 수 있도록 코드를 만든다.
- 예컨대 vector를 사용하는 부분을 만들면서 언제든지 deque나 list를 쓸 수 있는 여지를 남긴다던가.
시퀀스 컨테이너와 연관 컨테이너를 일반화한다?
- 대다수의 멤버 함수들은 한쪽 컨테이너에만 치우쳐 들어있다.
- push_back, push_front 등 : 시퀀스 컨테이너에서만 지원
- count, lower_bound 등 : 연관 컨테이너에서만 지원
- insert, erase 등 : 양쪽 모두에 있지만, 동작 원리가 다르다.
- insert : 시퀀스 컨테이너에서는 객체의 위치가 유지되지만, 연관 컨테이너는 자체 정렬 방식에 맞춰 객체를 옮긴다.
- erase : 시퀀스 컨테이너에서는 반복자가 새로 리턴되지만, 연관 컨테이너에서는 아무것도 리턴하지 않는다.(항목 9 참조)
- 그러므로 일반화는 불가능하다.
가장 많이 쓰이는 시퀀스 컨테이너인 vector, deque, list에 대해서만 일반화 한다?
- 각 컨테이너의 공통 함수만을 사용하도록 코딩한다.
- 즉, 공통되지 않은 기능들을 사용하지 않겠다는 의미이다.
- deque, list에서 제공하지 않는 기능
- reserve, capacity 등을 사용할 수 없다.(항목 14 참조)
- list에서 제공하지 않는 기능
- operator[]를 사용할 수 없다.
- 즉, 임의 접근 반복자를 사용하는 알고리즘도 사용할 수 없다는 뜻이다.
- sort, stable-sort, partial_sort, nth_element (항목 31 참조)
- vector에서 제공하지 않는 기능
- push_front, pop_front를 사용할 수 없다.
- vector, deque에서 제공하지 않는 기능
- splice, 멤버 함수 sort를 사용할 수 없다.
- 결국 따져보면, 일반화 시퀀스 컨테이너에 대해 호출할 수 있는 sort는 아무것도 없다.
- 또한 반복자, 포인터, 참조자를 무효화시키는 방식이 각 컨테이너마다 다르다.
- insert를 호출하면 모든 반복자, 포인터, 참조자가 무효화된다고 봐야 한다.
- deque::insert는 모든 반복자를 무효화하며, capacity가 지원되지 않는다.
- vector::insert는 모든 포인터와 참조자를 무효화한다.
- deque는 포인터와 참조자를 그대로 두고 반복자를 무효화한다.
- 결국, erase 역시 마찬가지로 모두 무효화된다고 봐야 한다.
- insert를 호출하면 모든 반복자, 포인터, 참조자가 무효화된다고 봐야 한다.
- C 인터페이스로 컨테이너 데이터를 넘기는 것도 불가능해진다.
- 이는 vector만 가능한 사양이기 때문이다. (항목 16 참조)
- bool 타입을 관리하는 컨테이너를 템플릿 인스턴스로 만들 수도 없다.
- vector<bool>은 절대로 vector처럼 동작하지 않고, 실제로 bool 데이터를 저장하지도 않는다. (항목 18 참조)
- list에서 상수 시간에 이루어지는 요소 삽입과 삭제를 기대해선 안된다.
- vector와 deque에서는 선형 시간의 복잡도를 가지기 때문이다.
셋 중 list를 지원하지 않도록 하면?
- 위 문제들 중 여전히 남는 문제들..
- reserve, capacity, push_front, pop_front는 사용할 수 없다.
- insert와 erase는 선형 시간이 걸리고 모든 것이 무효화된다.
- C와의 데이터 호환성이 불가능하다.
- bool 데이터를 저장하는 컨테이너가 불가능하다.
연관 컨테이너에 대해서만 일반화 한다?
- 이 경우 set, map에 대해 코딩하는 것은 불가능하다.
- set은 하나의 객체, map은 객체의 pair을 저장하므로 일반화할 수 없다.
- insert 멤버 함수의 리턴 타입 역시 다르므로 여전히 일반화 불가능하다.
- set, multiset(혹은 map, multimap)에 대해 코딩하는 것도 힘들다.
- 컨테이너에 값의 사본이 몇 개나 저장될지를 판단할 수 없어지기 때문이다.
- operator[]를 사용할 수 없다. 이는 map에서만 지원된다.
컨테이너 캡슐화
- STL 컨테이너는 각자 자신만의 장단점이 있다.
- 애초에 서로 바꾸어서 쓸 수 있도록 설계되지 않았다.
- 그렇다면, 만약 코딩 중 컨테이너를 바꿔야 하는 상황이 생긴다면 어떻게 해결해야 하나?
- 새로 바꾼 컨테이너를 사용하는 모든 코드를 테스트하며, 수행 성능, 반복자나 포인터나 참조자의 무효화 방식 등등을 모두 체크해야 한다.
- 예컨대 vector를 다른 것으로 바꾸었다면, 우선 C와 호환되는 메모리 배열 구조에 의존했던 코드를 바꿔야 할 것이고, 다른 것을 쓰다가 vector로 바꾸었다면, bool을 저장하던 것들을 다른 방식으로 수정해야 할 것이다.
- 즉, 너무 많은 품이 든다.
- 경우에 따라 수시로 컨테이너 타입을 바꿀 수밖에 없다면, 캡슐화를 사용하자.
방법 1 : 컨테이너와 반복자 타입에 대해 typedef를 사용한다.
class Widget { ... };
// 원래는 이렇게 코딩할 것을
vector<Widget> vw;
Widget bestWidget;
...
vector<Widget>::iterator i = find(vw.begin(), vw.end(), bestWidget);
// 이렇게 수정한다.
typedef vector<Widget> WidgetContainer;
typedef WidgetContainer::iteractor WCIterator;
WidgetContainer vw;
Widget bestWidget;
...
WCIteractor i = find(vw.begin(), vw.end(), bestWidget);
- 이 경우 컨테이너 타입을 일일이 수정하는 것보다는 훨씬 쉽게 수정할 수 있다.
- 컨테이너에 커스텀 할당자(allocator)를 붙이는 경우도 훨씬 손쉬워진다.
class Widget { ... };
template<typename T>
SpecialAllocator { ... };
typedef vector< Widget, SpecialAllocator<Widget> > WidgetContainer; // 할당자 적용
// 아래 코드는 수정할 필요가 없다.
typedef WidgetContainer::iterator WCIterator;
WidgetContainer vw;
Widget bestWidget;
...
WCIterator i = find(vw.begin(), vw.end(), bestWidget);
- 반복자를 typedef로 하는 경우, 이 긴 타입명을 일일이 칠 필요가 없다는 장점이 있다.
- 하지만 모던 C++ 에서는 auto를 사용하면 해결되는 문제이다.
- 하지만 typedef는 무척 제한적인 캡슐화이다.
- 할 수 없는 것을 하지 못하도록 막는 장치가 없다.
방법 2 : 클래스를 사용하여 캡슐화한다.
- 클래스에 컨테이너를 숨기고, 필요한 기능만 인터페이스를 제공한다.
// 상품 구매자 리스트로 list<Customer> 캡슐화
class CustomerList
{
private:
typedef list<Customer> CustomerContainer;
typedef CustomerContainer::iterator CCIterator;
CustomerContainer customers;
public:
...
};
- 임의 접근 기능이 필요해졌다고 하면, vector나 deque로 변경할 수 있을 것이다.
- 허나, 여전히 변경 시에는 후처리가 필요하다.
- CustomerList의 멤버 함수와 모든 friend 멤버를 조사해서 변경 후의 영향(수행 성능, 반복자/포인터/참조자 무효화 등)에 대해 점검해야 한다.
728x90