*이 포스팅은 개인 학습을 위해 내용을 정리한 것이 목적입니다.
5.1 STL 컴포넌트
1) 컨테이너 : 특정 타입의 원소들의 집합을 다루는데 사용 .
2) 반복자 : 컬렉션 객체가 소유한 원소를 순회하기 위해서 사용 .
- 컬렉션 : 컨테이너의 전부 or 일부 집합 . 다른 컨테이너 타입에 대해서도 공통의 인터페이스를 제공
3) 알고리즘 : 컬렉션 객체가 소유한 원소들을 처리하기 위함 .( 검색 , 정렬 , 수정 또는 다른목적으로 사용 )
- 반복자를 사용하면 알고리즘은 단 하나만 존재하면 됨.
- STL 의 기본 개념은 데이터와 동작을 구분하는 것에 의미가있음 .
- 컨테이너클래스 ( 데이터관리 ), 알고리즘 ( 동작 ), 이 둘을 연결하는 반복자 -> 모든 알고리즘이 모든종류의 컨테이너와 상호작용이 가능함
5.2 컨테이너
* 컨테이너의 종류
1 ) 시퀀스 컨테이너 : 원소들이 순서를 가지고 자신의 위치를 가짐 .
2 ) 연관 컨테이너 : 모든 원소들이 정렬기준에 맞추어 자동 정렬되는 컬렉션임 .
5.2.1 시퀀스컨테이너
1) Vector
- 자신의 원소를 동적인 배열을 이용하여 관리 . 랜덤엑세스 지원함 .
2) Deque
- 앞 , 뒤 양쪽으로 증가가 가능해서 벡터와 달리 원소삽입이 빠름 . 중간삽입동작은 느림 . vector대비 재할
당 에서 유리
예제소스보기 접기
deque샘플소스
void dequetest1(){ deque<int> coll; for( int i = 1; i <= 50000 ; ++i){ coll.push_back( i); }
mt = clock(); for( i = 0 ; i < coll.size() ; ++i){ //cout << coll[i] << ' '; cout << coll.at(i) << ' '; } cout << endl; }
void dequetest2(){ deque<int> coll; for( int i = 1; i <= 50000 ; ++i){ coll.push_back( i); }
for( i = 0 ; i < coll.size() ; ++i){ cout << coll.back() << ' '; coll.pop_back(); }
cout << endl; }
-> push_front, push_back은 수행 시간이 매우 빠름(at()을 통한 접근 속도와 비슷)
접기
3) List
- 어느위치에서나 삽입 / 삭제가 빠름 . 그러나 순차접근만 가능하다는 단점 .
5.2.2 연관컨테이너
- 정렬기준에 따라 자동적으로 자신의 원소들을 정렬함 . 일반적으로 2 진트리처럼 구현되어있음 .
1) set
원소가 가진 값에 따라 정렬됨 . 같은 값을 지닌 원소들은 하나뿐임
2) multiset
중복을 허락하는 set 임
예제코드보기 접기
set/multiset예제코드
void settest(){
std::set<int> coll; coll.insert(3); coll.insert(1); coll.insert(6); coll.insert(4); coll.insert(7); coll.insert(2); coll.insert(5); pair<set<int>::iterator, bool> res = coll.insert(2); //set의 경우 res의 second가 0 (FALSE).
//multiset의 경우 작업 성공한다. (정렬되어 insert)
set<int>::const_iterator itr; for( itr = coll.begin(); itr != coll.end(); ++itr){ cout << *itr << ' '; } cout << endl;
}
접기
3) map
key/value 를 한 쌍으로 가지고 있는 컨테이너 . 각 원소들은 정렬 기준의 기본이 되는 키를 가지 고 있으
며 , 동일한 값을 가진 키는 단 하나만 존재 가능하다 .
4) multimap
중복을 허용하는 map.
예제소스보기 접기
예제소스
void maptest1(){ typedef map<int,string> ISMap; ISMap coll;
coll.insert(pair<int,string>(5, "tagged")); coll.insert(pair<int,string>(1, "de")); coll.insert(pair<int,string>(3, "a")); coll.insert(pair<int,string>(4, "a")); coll.insert(pair<int,string>(7, "aa")); coll.insert(pair<int,string>(8, "tagged")); //value는 중복이 허용된다. (multimap은 키도 중복 가능) coll.insert(pair<int,string>(2, "tagged2")); pair<map<int,string>::iterator, bool> res = coll.insert(pair<int,string>(2, "tag")); map<int,string>::iterator pos; for( pos = coll.begin() ; pos != coll.end() ; pos++){ cout << "(" << pos->first << ", " << pos->second << ")" << endl; } }
접기
5.2.3 컨테이너 어댑터
컨테이너를 바탕으로 구현한 어댑터
1) stack : LIFO
2) queue : FIFO
3) priority queue
- 우선순위를 지닌 원소들을 관리하는 컨테이너 . 프로그래머에 의해 제공되어질 수 있는 정렬 기 준을 기
반으로 함 .
5.3 반복자
- 컨테이너가 소유한 원소들을 순회할 수 있는 객체
1) * 연산자
- 현재 위치의 원소를 반환
2) ++ 연산자
- 다음 원소를 가리키기 위함
3) ==, !== 연산자
- 두 반복자가 같은 위치를 나타내는지 확인
4) = 연산자
- 반복자를 할당 ( 참조하는 원소의 위치 )
* 반 개방의 이점
1) 원소를 순회하는 루프의 단순화 . (!=end())
2) 빈 범위에 대한 별다른 처리 불필요
5.3.1 연관컨테이너예제
1) 연관배열과 같은 map
연관배열 : 인덱스로 임의의 타입을 가질 수 있는 배열 . [] 연산자가 기존의 [] 연산자와는 동작이 다름 .
5.3.2 반복자의 카테고리
추후 7.2 에서 다룸 . 단방향 , 양방향 , 랜덤액세스 등이 존재함
5.4 알고리즘
- 컨테이너에 저장된 원소를 처리 하기 위해 존재 . 알고리즘은 컨테이너클래스의 멤버가 아닌 전역함수 . 모
든 컨테이너에 대하여 하나의 알고리즘 함수만 존재 .
-> 반복자가 공통의 인터페이스를 가지므로 가능함
예제소스 접기
예제소스
void algotest1(){ vector<int> coll; vector<int>::iterator pos;
coll.push_back(4); coll.push_back(1); coll.push_back(3); coll.push_back(7); coll.push_back(6); coll.push_back(5); coll.push_back(2);
pos = min_element(coll.begin(), coll.end()); cout << "min : " << *pos << endl; //전체에서 최소 찾기 pos = max_element(coll.begin(), coll.end()); cout << "max : " << *pos << endl; //전체에서 최대 찾기
pos = find(coll.begin(), coll.end(), 7); //4,1,3,7,6,5,2 에서 7위치 찾기 pos = min_element(pos, coll.end()); // 7~2 위치 중 최소값 찾기
cout << "min2 : " << *pos << endl; sort( coll.begin(), coll.end()); //정렬
pos = find(coll.begin(), coll.end(), 3); reverse( pos, coll.end()); //3위치부터 끝까지 모두 뒤집기 for( pos = coll.begin() ; pos != coll.end() ; ++pos){ cout << *pos << ' '; } cout << endl; }
결과
min : 1
max : 7
min2 : 2
1 2 7 6 5 4 3
접기
5.4.1 범위
- 모든 알고리즘은 하나 이상의 범위를 처리 : 컨테이너 전체를 다룰 수 있게 함 .
- 컨테이너의 처음과 끝 범위를 지정할 수 있음 . 그러나 이 경우 두 인자가 모두 유효한지 확인해야 함 .
예제보기 접기
소스 예제
void algotest2(){ list<int> coll; list<int>::iterator pos;
for( int i = 20 ; i < 40 ; i++) { coll.push_back(i); }
pos = find(coll.begin() , coll.end(), 3); reverse(pos, coll.end()); list<int>::iterator pos25, pos35; pos25 = find(coll.begin(), coll.end(), 25); pos35 = find(coll.begin(), coll.end(), 35);
cout << "max : " << *(max_element(pos25, pos35)) << endl; cout << "max2 : " << *(max_element(pos25, ++pos35)) << endl; }
결과
max : 34
max2 : 35
> 위 결과에서 3을찾는 위치 (pos)는 결과를 찾지 못해 coll.end()와 같다. 따라서 reverse()를 해도 범위가
[end(), end()) 범위여서 반전되는 경우는 없다.
> [pos25, pos35) 에서 최대값 검색을 하면 pos35 (35가 있는 위치)는 포함이 안되어 max는 34가 리턴됨
접기
5.4.2 다중범위
- 하나 이상의 범위를 처리한다 . 다중범위를 사용할 경우 , 두 번째에 제공되는 범위는 처음 범위보다 최소
한 첫 번째 제공한 범위보다는 커야한다 .
예제보기 접기
예제
void algotest3(){ list<int> col1; vector<int> col2;
for( int i = 1 ; i <= 9 ; ++i){ col1.push_back(i); }
col2.reserve(col1.size()/2);
copy( col1.begin(), col1.end(), col2.begin()); //runtime error난다. }
접기
- 대상의 공간이 부족하면 resize를 이용하여 해결하면 된다. 그런데 deque는 vector와 다르게 런타임에러가 없다.
예제보기 접기
예제
void algotest4(){ list<int> col1; vector<int> col2;
for( int i = 1 ; i <= 9 ; ++i){ col1.push_back(i); }
//col2.reserve(col1.size()); //resize대신 reserve로 충분한 공간 확보해줘도 된다. col2.resize(col1.size()); //단 resize/reserve모두 충분한 공간 없으면 런타임 에러 copy( col1.begin(), col1.end(), col2.begin()); deque<int> col3(col1.size()/2); col3.resize(col1.size()); copy(col1.begin(), col1.end(), col3.begin()); for( deque<int>::iterator pos2 = col3.begin() ; pos2 != col3.end() ; ++pos2){ cout << *pos2 << ' '; } cout << endl; }
결과
col3.resize(col1.size());를 수행하지 않은경우
1 2 3 4
col3.resize(col1.size());를 수행한 경우
1 2 3 4 5 6 7 8 9
접기
5.5 반복자 어댑터
- 반복자는 추상적인 존재 . 반복자의 인터페이스를 가지는 클래스를 작성할 수 있음 .
상세 내역은 7.4 에서 다시 기술
5.5.1 삽입 반복자 .
- 타겟의 사이즈를 조정 증가하여 줌 . 역할은 문자대로 inserter 임 .
1) 후위삽입반복자
- push_back() 을 이용하여 컨테이너에 객체를 옮김
2) 전위삽입반복자
- push_front() 을 이용 . list(), deque 등에서만 사용가능
3) 일반적 삽입 반복자
- Inserter. 일반적인 삽입 반복자의 두 번째 인자로 들어온 위치 앞에 원소를 삽입함.
예제보기 접기
반복자 예제코드
void algotest5(){ list<int> col1;
for( int i = 1 ; i <= 9 ; i++){ col1.push_back(i); }
vector<int> col2; copy(col1.begin(), col1.end(), back_inserter(col2));
deque<int> col3; copy(col1.begin(), col1.end(), front_inserter(col3));
set<int> col4; copy(col1.begin(), col1.end(), inserter(col4, col4.begin()));
for( vector<int>::iterator pos1 = col2.begin() ; pos1 != col2.end() ; pos1++){ cout << *pos1 << ' '; } cout << endl;
for( deque<int>::iterator pos2 = col3.begin() ; pos2 != col3.end() ; pos2++){ cout << *pos2 << ' '; } cout << endl;
for( set<int>::iterator pos3 = col4.begin() ; pos3 != col4.end() ; pos3++){ cout << *pos3 << ' '; } cout << endl; }
결과
1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1
1 2 3 4 5 6 7 8 9
> deque의 경우 front_inserter로 삽입하여 begin에 9, end에 1이 들어갔음
접기
5.5.2 스트림반복자
- 스트림에서의 읽기 / 쓰기가 가능한 반복자 .
예제더보기 접기
예제
void itrtest1() { vector<string> col1;
copy(istream_iterator<string>(cin), istream_iterator<string>(), back_inserter(col1)); sort(col1.begin(), col1.end()); vector<string>::iterator pos; unique_copy( col1.begin(), col1.end(), ostream_iterator<string>(cout,"\n")); }
접기
5.5.3 역방향 반복자
- 역순 모드로 동작 . 컨테이너에서 rbegin(), rend() 를 이용하여 생성가능 .
예제더보기 접기
예제소스
void reviter() { vector<int> coll;
for (int i=1; i<=9; ++i) { coll.push_back(i); }
copy (coll.rbegin(), coll.rend(), ostream_iterator<int>(cout," ")); cout << endl; }
접기
5.6 조작 알고리즘
- 여러 알고리즘은 타겟에 제공된 범위를 수정 할 수 있다 . 원소의 제거도 가능 .
5.6.1 원소의 제거
- remove()
주어진 범위에서 특정 원소를 제거 . 원소 개수를 변경하지 못함 . remove 함수에서 종료시 논리적
인 종료 반복자를 리턴하여 주므로 , 해당 반복자를 사용하여 원소의 끝을 알 수 있다 .
예제더보기 접기
예제)
void remove(){ list<int> coll;
for (int i=1; i<=6; ++i) { coll.push_front(i); coll.push_back(i); }
cout << "pre: "; copy (coll.begin(), coll.end(), ostream_iterator<int>(cout," ")); cout << endl;
remove (coll.begin(), coll.end(), 3);
cout << "post: "; copy (coll.begin(), coll.end(), ostream_iterator<int>(cout," ")); cout << endl; }
결과 :
pre : 6 5 4 3 2 1 1 2 3 4 5 6
post :6 5 4 2 1 1 2 4 5 6 5 6
> remove는 실제 객체를 삭제하는 것이 아니다!
접기
- erase()
컨테이너에서 실제로 원소를 제거할 때 사용됨 .
예제더보기 접기
예제
void remove2(){ list<int> coll;
for (int i=1; i<=6; ++i) { coll.push_front(i); coll.push_back(i); }
copy (coll.begin(), coll.end(),ostream_iterator<int>(cout," ")); cout << endl;
list<int>::iterator end = remove (coll.begin(), coll.end(), 3);
copy (coll.begin(), end, ostream_iterator<int>(cout," ")); cout << endl;
cout << "number of removed elements: " << distance(end,coll.end()) << endl;
coll.erase (end, coll.end()); copy (coll.begin(), coll.end(), ostream_iterator<int>(cout," ")); cout << endl; }
결과
6 5 4 3 2 1 1 2 3 4 5 6
6 5 4 2 1 1 2 4 5 6
number of removed elements : 2
6 5 4 2 1 1 2 4 5 6
접기
5.6.2 연관컨테이너와 조작 알고리즘
- 연관컨테이너는 데이터 삽입시 정렬되어 들어가는데 , 조작알고리즘이 사용되면 , 연관컨테이너의 자 동정
렬이라는 규칙이 깨어짐 . 각 컨테이너의 erase() 멤버함수를 호출하여 데이터를 제거 . 해당 함수는 삭제
되는 원소의 개수를 리턴하여줌 .
예제더보기 접기
예제)
void erasecon(){ set<int> coll;
for (int i=1; i<=9; ++i) { coll.insert(i); }
copy (coll.begin(), coll.end(), ostream_iterator<int>(cout," ")); cout << endl;
int num = coll.erase(3); cout << "number of removed elements: " << num << endl; copy (coll.begin(), coll.end(), ostream_iterator<int>(cout," ")); cout << endl; }
결과
1 2 3 4 5 6 7 8 9
1 2 4 5 6 7 8 9
접기
5.6.3 알고리즘과 멤버함수
- 알고리즘의 경우 범용적이므로 성능에서는 불리 . 멤버함수의 경우 해당 컨테이너에 따른 더 최적화된 구
현을 사용할 수 있음 .
예제더보기 접기
예) 리스트를 이용한 원소 제거
void itrtest6(){ list<int> coll;
for (int i=1; i<=6; ++i) { coll.push_front(i); coll.push_back(i); }
coll.erase (remove(coll.begin(),coll.end(), 3), coll.end()); coll.remove (4); for( list<int>::iterator itr = coll.begin(); itr != coll.end() ; ++itr){ cout << *itr << ' '; } cout << endl; }
결과
6 5 2 1 1 2 5 6
접기
5.7 사용자 제네릭함수들
- STL 은 확장 가능한 프레임워크 . 따라서 사용자가 정의하는 함수 , 알고리즘 사용가능 . 그러나 올바른 반
복자의 사용을 위해 컨테이너의 타입을 이용하여야만 한다 .
예제더보기 접기
예제소스
template <class T> void print_elements(const T& coll, const char* optcstr=""){ typename T::const_iterator pos;
cout <<optcstr; for( pos = coll.begin() ; pos != coll.end() ; pos++){ cout << *pos << ' '; } cout << endl; }
void printtest(){ list<int> con1; deque<int> con2; set<int> con3;
for( int i = 1 ; i <= 9 ; ++i){ con1.push_back( i); con2.push_back( i); con3.insert(i); }
print_elements( con1, "List:"); print_elements( con2, "deque:"); print_elements( con3, "set: "); }
> 템플릿을 하나만 두어도 list, set, deque에 대해 모두 동작한다.
접기
5.8 알고리즘 인자로서의 함수
- 알고리즘은 사용자가 정의한 함수도 인자로 받는 것을 허용함 .
5.8.1 사용예
ex) for_each()
void print(int elem)
{
cout << elem << endl;
}
int main()
{
…
for_each(coll.begin(), coll.end(), print);
}
위와 같이 함수를 인자로 넘겨 사용할 수 있음
예제더보기 접기
예제)
int square( int value){ return value * value; }
void transform1(){ //set<int> col1; vector<int> col1;
for( int i = 1 ; i <= 9 ; ++i) { //col1.insert(i); col1.push_back(i); }
print_elements(col1, "init:"); //transform( col1.begin(), col1.end(), back_inserter(col2), square); transform( col1.begin(), col1.end(), col1.begin(), square); // transform은 범위 중복이 된다. print_elements(col1, "squared:"); }
> transform범위가 중복되어도 동작한다. -> 컨테이너의 데이터를 수정하는데 사용하여도 좋다. copy는 수정이 불가하다.
접기
5.8.2 조건자 (Predicates)
- boolean 을 반환하는 함수 . 단항조건자 ( 하나의 원소값을 체크 ) 와 이항조건자 ( 두개의 원소 속성을 비교 )
단항 조건자는 찾을 때 유리하다.
단항조건아 예제 접기
가 있음 . 조건자에는 내부 상태를 변경하는 것은 허용되지 않는다 .
단항조건자 예제
bool isPrime(int number){ number = abs(number);
if( number == 0 || number == 1){ return true; }
int divisor; for( divisor = number / 2; (number%divisor) != 0 ; --divisor){}
return (divisor == 1); }
void unarypredic(){ list<int> col;
for( int i = 24 ; i <= 31 ; ++i){ col.push_back(i); }
list<int>::iterator pos; pos = find_if(col.begin(), col.end(), isPrime); if( pos != col.end()){ cout << *pos << "is prime." << endl; } else { cout << "no prime number" << endl; } }
> find_if는 조건자(여기서는 isPrime)의 첫 번째 인수를 넘겨준다. 두번째 이후 인자를 찾기 위해서는 find_if를 또 호출
하면 된다. find_if( ++pos, col.end(), isPrime)으로, 단, 여기서 주의할 것은 찾아온 pos 이후의 인자부터 검색해야 한
다. 그래서 ++pos로 인자를 줄것! (아니면 그 앞에서 pos를 증가할것, pos에 대해 후위증가연산(pos++)을 하면 무한
루프를 만날 수있다. 모든 범위는 [start,end)이다. (즉, start는 포함범위다)
접기
이항조건자는 정렬등에서 유리하다
이항조건자예제 접기
이항조건자 예제
class Person{ public: Person() : fn("test"), ln("test"){} Person(const string& f, const string& l) : fn(f), ln(l) {};
string firstname() const ; string lastname() const ; private: string fn; string ln; };
inline string Person::firstname() const { return fn; } inline string Person::lastname() const { return ln; }
bool personSortCriterion(const Person& p1, const Person& p2){ return (p1.lastname() < p2.lastname()) || (!(p2.lastname() == p1.lastname()) && (p1.firstname() <
p2.firstname())); }
void binarypred(){ Person p1("a", "aa"); Person p2("bill", "bill"); Person p3("a", "bill"); Person p4("bill", "aa"); Person p5("bill", "aaa"); Person p6("billl", "aa");
deque<Person> col; col.push_back( p1); col.push_back( p2); col.push_back( p3); col.push_back( p4); col.push_back( p5); col.push_back( p6);
deque<Person>::iterator itr; for( itr = col.begin() ; itr != col.end() ; ++itr){ cout << itr->lastname() << " " << itr->firstname() << endl; } sort(col.begin(), col.end(), personSortCriterion); cout << "after sort" << endl; for( itr = col.begin() ; itr != col.end() ; ++itr){ cout << itr->lastname() << " " << itr->firstname() << endl; } }
> personSortCriterion을 정렬 기준으로 위 deque을 정렬했다. 정렬은 사전순 정렬이다.
접기
5.9 함수 - 객체
5.9.1 함수 - 객체란 ?
- 함수처럼 행동하는 객체를 의미 . () 연산자를 오버로딩하여 함수처럼 사용하는 것 .
예제 보기 접기
예제
class PrintInt{ //PrintInt가 함수객체. public: void operator() (int elem) const{ cout << elem << ' '; } };
void funcobj(){ vector<int> col; initCon( col, 1, 9 );
for_each(col.begin(), col.end(), PrintInt()); cout << endl; } //위와 같이 사용 가능하다.
접기
- 함수객체의 장점
1) 함수 객체는 스마트함수
- 다른 멤버 변수나 다른 멤버 함수를 가질 수 있다 . 즉 , 함수객체는 상태를 가지고 있다 . 런타임 시에 함
수객체를 초기화 할 수 있다 .
예제보기 접기
예제)
위 PrintInt에서 특정 값을 더하여 출력하고 싶다면 다음과 같이 작성해 보자.
class PrintInt{ public: PrintInt(){ m_nAdd = 0;} PrintInt(int num){ m_nAdd = num; } void operator() (int elem) const{ cout << (elem + m_nAdd) << ' '; } private: int m_nAdd; };
void funcobj(){ vector<int> col; initCon( col, 1, 9 );
for_each(col.begin(), col.end(), PrintInt()); for_each(col.begin(), col.end(), PrintInt(20)); cout << endl; }
> 위와 같이 하나의 함수에서 다른 동작을 수행하도록 할 수 있다.
접기
2) 각각의 함수객체는 자신만의 타입을 가지고 있다 .
- 기존의 함수는 함수 시그니처가 다른 경우에만 다른 타입을 가질 수 있다 . 그러나 함수 객체는 시그니
처가 동일한 경우에도 다른 타입을 가질 수 있다 .
* 함수 시그니처 : 함수 이름과 함수의 인자 목록 .
3) 함수 객체는 기존의 함수보다 빠르다 .
- 컴파일 타임에 자세한 정보를 정의하므로 더 좋은 최적화를 제공함 .
5.9.2 미리 정의된 함수객체
- C++ 표준 라이브러리에 기본적으로 정의된 여러가지의 함수객체가 존재함 .
less<int> : 적은 값
greater<int> : 큰값
negate<int> : 부정연산 (-)
multiplies<int>: 값 곱하기
등 여러 함수객체가 존재함 .
예제더보기 접기
예제
void fo(){ set<int, greater<int> > col1; // set<int, greater<int>> 는 컴파일에러.;; deque<int> col2;
initSetMap(col1, 1, 9);
print_elems(col1, "init");
transform(col1.begin(), col1.end(), back_inserter(col2), bind2nd(multiplies<int>(),10)); print_elems(col2, "transformed");
replace_if(col2.begin(), col2.end(), bind2nd(equal_to<int>(), 70), 42); print_elems(col2, "replaced");
col2.erase(remove_if(col2.begin(), col2.end(), bind2nd(less<int>(), 50)), col2.end()); print_elems(col2, "removed"); }
> transform, replace_if는 하나의 인자를 원한다. 그래서 두개의 인자를 하나로 합쳐야함. 이를 위해 bind2nd를 사용
하여 두 인자를 하나로 동작하게 합쳐준다. [multiplies, 10]은 *10으로 , [equal_to, 70]은 ==70으로, [less, 50]은 <
50으로 합쳐서 두 인자를 하나로 동작하게 만들어 주는 bind2nd이다. (bind1st도 있음)
접기
5.10 컨테이너 원소
- 컨테이너는 소유한 원소들을 특별한 방법으로 관리함
5.10.1 컨테이너 원소들이 갖추어야 할 사항들
- 기본적으로 충족되어야 할 요구사항
1) 원소들은 복사 생성자에 의해서 복사되어 질 수 있어야 한다 .
- 복사된 객체는 원본과 동일하여야 한다 . 만약 객체를 복사하는데 많은 시간이 필요할 경우 컨테 이너의
레퍼런스 의미론을 사용하여 이를 피할 수 있음 . (6.8 에서 다룸 )
2) 원소들은 할당연산자들에 의해서 할당되어 질 수 있어야만 한다 .
3) 모든 원소들은 소멸자에 의해 파괴될 수 있어야 한다 .
- 추가로 원소에 대해 다음의 요구 사항도 충족해야 한다 .
1) 시퀀스 컨테이너의 멤버함수를 위해 기본생성자는 반드시 존재할 것
2) 위 경우에 따라 , 동일성 검사를 위한 == 연산자는 정의되어야 함 . ( 특히 원소 검색시 )
3) 연관 컨테이너에 대해서는 정렬의 기준이 반드시 필요함 .
5.10.2 값 의미론과 레퍼런스 의미론
- 값 의미론 : 객체의 값을 복사해서 가짐 .
레퍼런스 의미론 : 객체의 주소를 가져옴 .
* 값 의미론만을 지원하는 STL 의 장점과 단점
1) 장점
- 원소를 복사하는 것은 간단함 .
- 레퍼런스는 에러 발생이 쉬움 . 존재하지 않는 객체에 대해 참조하지 않는다는 것을 보장해야 함 . 또한
순환레퍼런스에 대해서도 관리가 필요
2) 단점
- 원소 복사는 나쁜성능을 가져올 수 있음 . 복사가 불가능할수 있음
- 같은 객체를 여러 컨테이너에 동시에 관리하는 것이 불가능
- 레퍼런스 의미론을 구현하기 위해서는 원소로서 포인터를 사용하는 것 . 그러나 대상이 참조될 지 여부가
불투명하므로 작업에 많은 주의가 필요 .
- 스마트포인터를 사용하는 것이 더 좋은 접근방법일 수 있음 . 그러나 auto_ptr 은 컨테이너원소의 기본 요
구사항을 충족하지 못한다 . 이유는 , 복사나 할당 후 , 원본과 복사본이 다르기 때문 . 따라서 직접 스마트
포인터를 작성해야 함 . (6.8 참조 )
5.11 STL 에서의 에러 및 예외
5.11.1 에러핸들링
- STL 의 목표는 안정성보다는 성능에 있음 . 따라서 에러 검사가 기본적으로 수행되지 않음 .
* STL 사용에 따른 지켜야 할 요구사항
1) 반복자는 반드시 유효해야 함 . vector 나 deque 에서 원소의 삽입 / 제거 ,, 메모리공간 재할당 시 , 반복자
가 무효화 되므로 초기화 필요함 .
2) 종료위치 다음을 가리키는 반복자는 참조하는 원소가 없으므로 , *, -> 연산자 사용불가
3) 범위는 반드시 유효 해야만 한다 .
- 범위를 나타내는 반복자는 반드시 같은 컨테이너를 참조할 것
- 두 번째 반복자는 첫 번째 반복자로부터 도달할 수 있을 것 .
4) 하나 이상의 소스 범위가 사용된다면 , 두 번째 또는 그 이후의 범위는 첫 번째 범위보다 더 많 은 범위
를 가져야 함
5) 목적지 범위는 덮어쓸 수 있도록 하여야 함 .
5.11.2 예외처리
- 예외처리를 위한 코드 는 성능의 저하를 유발함 . STL은 원래 안정성이 아닌 성능을 목표로 고안 되었음.
-> 트랜잭션 안정성에 대한 요구 생김 .
- C++ 표준 라이브러리 보장내용
1) 모든 노드 기반의 컨테이너에 대해 노드 구성중 실패가 발생시 , 노드는 컨테이너를 빠져나감 . 그러나
list 의 경우 모든 연산에 대해 트랜잭션 안정성을 충족한다 .
2) 배열 기반의 컨테이너는 원소 삽입시 완전한 복구능력이 없다 . 따라서 복구를 위해서는 복사본 이 필
요함 . 그러나 push, pop 의 경우 컨테이너에 영향주지 않도록 보장하여줌 .
참조 : C++ Standard Library - A tutorial and Reference
댓글