요약하면 표준은 아니지만 거의 표준화된 인터페이스를 제공한다. 하지만 좀 다르다. -_-;
딩컴웨어와 SGI 버젼으로 구분해서 볼수 있다.
map이나 multimap 등의 [] 연산자는 두가지 동작을 같는다. 첫번째는 해당 키값의 데이타를 갱신하는 것이고 두번째로 해당 키값의 데이타가 컨테이너에 존재하지 않는다면 추가를 하는 동작이다.
map이나 multimap 등의 [] 연산자는 두가지 동작을 같는다. 첫번째는 해당 키값의 데이타를 갱신하는 것이고 두번째로 해당 키값의 데이타가 컨테이너에 존재하지 않는다면 추가를 하는 동작이다.
이 오버헤드는 일반적으로 기본생성자가 아닌 특정 데이타 타입을 지원의 생성자를 지원하는 클래스에서 문제가 되는데
class Widget{
public:
Widget();
Widget(double weight);
Widget& operator=(double weight);
}
map<int, Widget> m;
m[1] = 1.50;
typedef map<int, Widget> IntWidgetMap;
pair<IntWidgetMap::iterator, bool> result = m.insert(IntWidgetMap::value_type(1, Widget()));
result.first->second = 1.50;
m.insert(IntWidgetMap::value_type(1,1.50));
// [] 연산자를 이용한 갱신
m[k] = v;// insert를 이용한 갱신
m.insert(IntWidgetMap::value_type(k,v)).first->second = v;
(lower_bound 의 리턴값은 약간 특이. EffectiveSTL45 참조)
연관 컨테이너 대신에 정렬된 vector를 쓰는 것이 좋을 때가 있다
빠른 데이타 검색을 지원하는 자료구조가 필요할 경우 많은 경우 연관 컨테이너를 사용한다. (물론 비표준의 해쉬컨테이너가 더 빠를것이다.)
하지만 vector를 정렬되어 있다고 가정할 경우 대부분의 검색 속도에서 vector가 더욱 빠른 검색 속도를 보여준다. 연관 컨테이너는 전형적으로 이진 탐색 트리로 구현되어 있다. 이 트리는 테이터 노드의 삽입, 삭제, 탐색이 임의대로 아무 때나 이루어지는 상황에 적합하도록 구현된 자료구조 이다. 하지만 많은 상황에서 그런 복잡한 구조를 가지지는 않을 것이다.
- 데이터 셋업, 생성
- 데이터 탐색
- 데이터 재구성
- 하나의 데이터당 사용하는 메모리의 크기
연관 컨테이너의 경우 노드가 포인터로 연결된 구조를 사용하기 때문에 포인터 만큼의 메모리를 더 사용한다. (최소 포인터 3개)
- 메모리 참조위치의 근접성
vector 같은 연속 메모리 구조를 취하는 데이터구조는 논리적으로 인접해 있는 데이터가 물리적으로도 인접해 있게 되기 때문에 메모리 접근에 지역성에 따른 속도 계산을 생각할수 있다.
set과 multiset에 저장된 데이터 요소에 대해 키(key)를 바꾸는 일은 피하자
우선 알아둬야 할 사항은 set과 multiset의 경우 하나의 값을 넣는대 그 값이 순서를 유지하는 역할인 Key값이 되므로 변경해서는 안된다. 하지만 구조체나 클래스 일경우 그 일부분만 Key가 되고 나머지 부분은 그저 데이타일 뿐이므로 변경을 해도 set의 순서가 엉망이 되는 경우는 없다. 그렇게 때문에 일반적으로 set의 데이타타입은 변경이 가능하도록 const 형이 아니도록 되어있다.
(모호한 표준안에 의해 다른 해석을 한 STL구현방식 이라고 설명하고 있음)
// Object는 set에서 Key역할을 하는 ID와 기타 데이타로 구성
set<Object>::iterator i = se.find( objectID );// 밑의 코드는 일부 STL에서는 적용되지 않는다.
if( i != se.end() ) {
i->SetName(“yoway”); // Key가 아닌 데이타는 변경하고 싶다.
}…
// 아래처럼 캐스팅을 사용한다.
if( i != se.end() ) {
const_cast<Object&>(*i).SetName(“yoway”);
}…
// 아래처럼 하면 안된다.
if( i != se.end() ) {
static_cast<Object>(*i).SetName(“yoway”);
}
캐스팅을 사용하고 싶지 않다면
- 변경하고자 하는 컨테이너 요소의 위치를 찾는다.(EffectiveSTL45 참조)
- 변경하고자 하는 요소의 복사본을 만든다. (map의 Key를 변경하는 경우는 복사본의 Key의 경우는 const로 하지 않는다.)
- 컨테이너에서 해당 요소를 삭제한다. (EffectiveSTL09 참조)
- 만들어둔 복사본의 정보를 원하는대로 바꾼다.
- 복사본을 컨테이너에 삽입한다. 만약 새로 삽입되는 복사본의 위치가 제가된 요소의 위치와 같거나 그 옆(즉 Key같이 변경되지 않았을 경우)이면 윗 단계에서 요소의 위치를 파악했던 반복자를 이용해서 삽입함으로써 상수시간에 삽입이 가능하다.
// Object는 set에서 Key역할을 하는 ID와 기타 데이타로 구성
set<Object>::iterator i = se.find( objectID );
if( i != se.end() ) {
Object o(*i);
se.erase(i++);o.SetName(“yoway”);
se.insert(i,o);
}
연관컨테이너용 비교 함수는 같은 값에 대해 false를 반환해야 한다.
set<int, less_equal<int> 라는 연관 컨테이너를 선언하고 사용할때 똑같은 정수형을 두개를 넣개 되면 두번째 값을 넣을대 해당 값이 있는지 검색하는 과정을 거치게 된다. 이 과정에서 일반적인 set 이라면
!(A < B ) && !(B < A) |
!(A <= B ) && !(B <= A) |
같은 값에 대해서 true를 반환하는 비교함수는 모두 동일한 결과를 얻게 된다.
포인터를 저장하는 연관컨테이너에 대해서는 적합한 비교(비교 함수자) 타입을 정해주자
set<string*> 같은 컨테이너를 사용할때 비교자를 설장해 주지 않는다면 set은 포인터의 크기를 사용한 비교를 하게 된다. 즉 포인터를 넣을때 포인터값으로 정렬되는 것을 원치 않는다면 그 포인터형을 받아서 적절히 비교 연산을 할수있는 비교 함수자가 필요한 것이다.
struct StringPtrLess :
public binary_function<const string*,
const string*,
bool>
{
bool operator()(const string* ps1, const string* ps2 ) const
{
return *ps1 < *ps2;
}
};typedef set<string*, StringPtrLess> StringPtrLess;
StringPtrSet ssp;
….
아랫 부분은 set을 선언하는 간단한 예시이다. 이후는 그냥 포인터를 넣어주면 된다.
string DerefenceLess{
template<typename PtrType>
bool operator()(PtrType pT1, PtrType pT2) const
{
return *pT1 < *pT2;
}
};…
set<string*, DerefenceLess> ssp; // set<string*, StringPtrLess> 와 똑같이 동작
상등관계와 동등관계의 차이를 파악하자
상등관계는 operator= 에 의해서 비교되는 것으로 두 객체가 나타낸는 값이 동일한지를 판단한다.
동등관계는 operator< 에 의해서 비교되는 것으로 두 객체가 다른가를 판단한다.
위의 동등을 코드로 표현해 보면
if( !(w1<w2) && !(w2<w1) )
{
// 동등하다
}
else
{
// 동등하지 않다.
}
!c.key_comp()(x,y) && !c.key_comp()(y,x)
vector 보기를 돌같이 하자
vector<bool> 은 STL 컨테이너가 아니다
만약 bool 컨테이너가 필요하다면 deque<bool> 이나 bitset 을 사용하도록 하자.
복잡복잡
쓸데없이 남은 용량은 “바꿔치기(swap)묘수”를 써서 업애 버리자
어떤 vector가 있다고 할때 push_back, insert 나 범위 생성자로 vector의 할당된 용량이 늘어나 버렸다면 erase 등을 호출 하여 데이타를 삭제한다고 해도 이미 할당된 용량이 줄어들지는 않는다.
이런 경우
vector<Object>(v).swap(v);
코드가 처리되는 방식은
- 복사생성자 에 의해 딱맞는 크기를 갖는 vector<Object> 임시객체가 v를 복사해서 생성
- swap() 에 의해서 백터의 데이타가 뒤바뀜
- 코드가 끝나면서 임시객체 소멸
- v는 타이트 해짐
또한 swap는 컨테이너를 완전히 비우고 용량을 라이브러리 구현코드가 허용하는 최소치로 줄이는 데에도 쓸 수 있다.
vector<Object> v;
string s;
…
vector<Obect>().swap(v);
string().swap(s);
기존의 C API에 vector와 string을 넘기는 방법을 알아두자
vector<int> v 라는 객체가 있을때 int* 를 받는 함수에 넘기는 방법은 &v[0] 을 사용하면 된다.
vector는 연속된 메모리로 컨테이너를 관리하므로 저렇게 넘기면 배열을 사용하는 C언어 함수에서도 문제될것이 없다.(반복자 begin을 사용하는 경우를 생각해 볼수 있지만 반복자는 엄연히 반복자객체이지 배열의 시작포인터를 의미하지는 않는다. vector에서 이 둘이 같은것을 의미할 지라도 두개의 개념은 분리해서 사용하는것이 옳다.)
주의사항
|