SlideShare a Scribd company logo
1 of 62
Effective STL
효과적인 컨테이너 요리법
Chapter
1
항목 1 : 적재적소에 알맞은 컨테이너를 사용하자
표준 STL 시퀀스 컨테이너 : vector, string, deque, list.
표준 STL 연관 컨테이너 : set, multiset, map, multimap.
비표준 시퀀스 컨테이너 : slist, rope
비표준 연관 컨테이너 : hash_set, hash_multiset, hash_map, hash_multimap
String 대신 사용되는 vector<char>
표준 연관 컨테이너 대신 사용되는 vector
STL에 속하지 않는 표준 컨테이너 : 배열, bitset, valarray, stack, queue, priority_queue
Vector, list, deque의 선택 기준
Vector : 기본적으로 사용되는 시퀀스
List : 시퀀스의 중간에 빈번한 삽입, 삭제가 수행될 필요가 있을때 사용
Deque는 대부분의 삽입과 삭제가 시퀀스의 앞과 끝에서 일어나는 경우
STL컨테이너는 연속 메모리 컨테이너와 노드 기반 컨테이너로 나눌 수 있다.
연속 메모리 컨테이너 – vector, string, deque
: 동적 할당된 하나 이상의 메모리 단위에다가 데이터 요소를 저장해 두는 컨테이너
새 요소가 삽입되거나 이미 있던 요소가 지워지면 재배열이 일어나 수행 성능에 발목을 잡는다.
노드 기반 컨테이너
: 동적 할당된 하나의 메모리 단위에다가 하나의 요소만을 저장
컨테이너 요소를 삽입 혹은 삭제 했을때 노드의 포인터만이 영향을 받지, 노드의 내용은 그대로이다.
연결 리스트를 나타내는 컨테이너, 즉 list나 slist가 노드 기반이고, 표준 연관 콘테이너 모두가 노드 기반
항목 2 : “컨테이너에 독립적인 코드"라는 환상을 조심하자.
Vector를 쓰는 부분을 만들면서 언제든지 vector대신 deque나 list를 쓸 수 있는 여지를 남겨 놓는 행위.
즉 컨테이너에 독립적인 코드를 작성하려고 기를 쓴다는것은 잘못된 이해에서 나오는 행동
반복자, 포인터, 참조자를 무효화시키는 방식이 컨테이너마다
다르다!
경우에 따라 수시로 컨테이너 타입을 바꿀 수밖에 없는 입장이라면, 변경을 조금 용이하게 해주는 방법이 있다.
“캡슐화”
항목 3 : 복사는 컨테이너 안의 객체에 맞게 비용은 최소화하며, 동작은 정확하게
어떤 컨테이너에 어떤 객체를 밀어 넣을때 컨테이너에 들어가는 것은 그 객체의 복사본이다.
컨테이너에서 어떤 객체를 뽑아낼 때는 컨테이너에 들어 있던 객체의 참조자를 얻어 원하는 객체에 복사한다.
복사되어 들어가고, 복사되어 나오는것이 STL의 방식
객체의 복사는 해당 클래스의 복사용 멤버 함수를 사용하여 이루어지는데,
특히 복사 생성자와 복사 대입 연산자가 사용된다.
STL에서 복사는 거의 모든 순간에 일어난다.
복사에 드는 비용이 큰 객체를 컨테이너에 넣을 때에는 그냥 단순히 객체를 컨테이너에 넣기만 하면
수행 성능의 병목현상을 일으키고 만다.
컨테이너에 더 많은 객체가 들어갈수록 시스템에 몰아치는 메모리 사용량과 CPU 사이클의 폭풍은 더욱 세진다.
Slice : 상속된 객체의 경우 복사 동작 중에 데이터가 없어지는 경우
기본 클래스 객체의 컨테이너를 만들어놓고 여기다가 파생 클래스 객체를 넣으면 Slice가 발생
포인터의 컨테이너 : 속도 빠르고, 정확하고, Slice문제에도 끄덕없는 복사를 쉽게 하는 방법
- 포인터의 컨테이너를 사용해서 복사를 하되 골치 거리를 피하는데에는 스마트 포인터가 괜찮은 방법
STL은 “불필요한” 복사를 피하도록 설계되어 있다.
: 이들 중 몇 개만 필요하든지, 아니면 기본으로 만들어진 값을 어딘가에서 읽어온 다른 값으로 덮어쓰고
싶다던지 해도 무조건 maxNumWidgets개의 Wiget에서 기본 생성자가 호출된다.
이런 배열 대신 vector를 사용하면 필요할 때마다 크기가 자라도록 할 수 있다.
항목 4 : size()의 결과를 0과 비교할 생각이라면 차라리 empty를 호출하자.
두 코드는 본질적으로 똑같은 코드이다.
하지만 empty가 들어간 코드를 쓰는것이 더 좋다.
Empty는 모든 표준 콘테이너에 대해 상수 시간에 수행되지만,
몇몇 제품에서 구현된 list 클래스에서 size가 선형 시간에 수행되기 때문이다.
항목 5 : 단일 요소를 단위로 동작하는 멤버 함수보다 요소의 범위를 단위로 동작하
는 멤버 함수가 낫다.
단일 요소 멤버 함수보다 범위 멤버 함수가 더 좋은 이유
1. 범위 멤버 함수를 사용한 코드가 대개 짧다.
2. 범위 멤버 함수는 훨씬 명확하고 간결한 의미를 전달한다.
단일 요소 멤버 함수에서는 메모리 할당자의 요구도 많고 객체 복사도 빈번하며 불필요한 연산도 자주
일어난다.
같은 일을 한다고 볼 때 범위 멤버 함수는 이보다 효율적이다.
범위 생성 : 모든 표준 컨테이너는 다음과 같은 형태의 생성자를 지원하고 있습니다.
범위 삽입 : 모든 표준 컨테이너는 다음과 같은 형태의 insert를 지원하고 있다.
범위 삭제 : 표준 컨테이너에서 범위 버전의 erase를 제공하고 있지만, 반환 타입은
시퀀스 컨테이너와 연관 컨테이너에 대해서 각각 다르다.
항목 6 : C++ 컴파일러의 어이 없는 분석 결과를 조심하자.
Int 데이터가 들어 있는 파일이 있고, 이 파일에 있는 int를 list에 복사한다고 할때
위 코드는 리스트 객체가 아니라 data라는 이름의 함수를 선언한 것이다! 그러므로 틀린 코드
함수의 매개변수에 괄호를 넣음으로써 해결할 수 있다.
Data라는 리스트 객체가 제대로 생성된다.
Istream_iterator가 매개 변수로 들어가고, 범위 생성자가 호출된다.
하지만 , 모든 컴파일러가 이렇지는 않다.
익명 객체 선언을 istream_iterator에 쓰지 말고, 각 반복자 객체의 이름을 만들어 주는 것!
항목 7 : new로 생성한 포인터의 컨테이너를 사용할 때에는
컨테이너가 소멸되기 전에 포인터를 delete한흔 일을 잊지 말자.
STL에서는 삽입과 삭제 떄에 필요한 어떤 메모리 관리도 알아서 척척 해준다.
객체가 몇 개나 들어있고, 최대 몇 개까지 담을 수 있는가에 대한 데이터도 가지고 있고, 자신이
메모리에서 인생을 마칠 때 그 때까지 가지고 있던 객체도 자동으로 없애준다.
하지만 new로 할당된 포인터를 컨테이너에 담는 경우에는 이야기가 다르다.
포인터의 컨테이너는 자신이 소멸될 때 각 요소 자체를 없애 주기는 하지만,
포인터의 소멸자가 아무 일도 하지 않는다는 것이다.
포인터에 대해 delete를 하지 않기 때문이다.
위 코드도 완벽한 코드가 아닌 무난한 코드이다!
For_each가 하는 일을 for 루프로 대신하고 있는데, 이것은 for_each만큼 명확하지 못하다.
또한 예외 안전성을 갖추고 있지 않다.
Vwp에 포인터가 들어있고, vwp 내부에 돌면서 delete를 수행할 직전에 예외가 발생하면,
불쌍한 메모리는 다시 샌다.
For_each와 비슷하게 만든 for 루프를 for_each로 바꾸려면 delete를 수행하는 부분을
함수 객체로 바꾸어야 한다.
위 함수를 사용하면 추적이 힘든 버그를 일으킬 수 있다.
가령, string을 상속한 클래스를 선언했다고 사정할 시,
String은 가상 소멸자를 가지고 있지 않아서 public으로 상속한 클래스는 C++의 패륜아가 된다!
DeleteOBject::operator()에 넘겨지는 포인터의 타입을 컴파일러가 직접 판단할 수 있도록 한다.
제대로 잘 동작하면서 타입 안전성도 갖추고 있는 코드이다.
하지만 아직도 예외 안전성이 없다!!
포인터 컨테이너 대신에 스마트 포인터의 컨테이너를 사용 하는 것 !
항목 8 : auto_ptr의 컨테이너는 절대로 만들지 말자.
항목 9 : 데이터를 삭제할 때에도 조심스럽게 선택할 것이 많다.
C에 들어있는 정수 중에 1963이라는 값을 가진 것은 모두 지우고 싶다!
연속 메모리 컨테이너 일때 :
표준 연관 컨테이너 일때는 rmove라는 이름을 가진 어떤 것도 소용이 없다.
삭제가 효율적으로 될 뿐만 아니라, 로그 시간만큼만 걸린다
또한 연관 컨테이너의 erase멤버 함수는 상등성이 아닌 동등성에 기반하고 있다는 이점!
표준 연관 컨테이너의 경우 이 문제가 위처럼 쉽게 해결되지 않는다.
2가지의 방법이 있다.
1. 코딩하기 쉽지만, 비효율적
2. 코딩하기 어렵지만, 효율적
1. 코딩하기 쉽지만, 비효율적인 방법
: remove_copy_if를 사용하여 컨테이너에서 없애지 말아야 할 값들을 새 컨테이너에 복사했다가
새 컨테이너와 원래의 컨테이너를 swap하는 것 : 복사에 걸리는 비용이 비싸다.
연관 컨테이너는 remove_if와 비슷한 멤버함수를 제공하지 않기 때문에, c내의 요소를
처음부터 횡단하는 루프를 넣어, 요소를 직접 검사해서 지워야 한다.
이번 항목의 결론
항목 10, 11.
이해가 잘 되지 않습니다…
항목 12 : STL 컨테이너의 쓰레드 안전성에 대한 기대는 현실에 맞추어 가지자.
많은 프로그래머들의 바람은 STL이 완벽하게 쓰레드 안전성을 가지고 있는 것이다.
자신들이 짊어져야 하는 동시성 제어의 부담에서 벗어날 수 있다고 생각한다.
컨테이너의 완벽한 쓰레드 안전성을 구현하려면 라이브러리 쪽에서 어떤 것을 해야 할지 정리해 보자.
다중 쓰레드 환경에서는 첫째 줄의 실행이 끝난 직후에 다른 쓰레드 하나가 v의 값을 바꾸는 일이 생김
둘째 줄에서 first5를 v.end()와 비교하는 것은 쓸모가 없다. -> v의 값은 첫째 줄의 마지막에 있을 때와 다르기 때문
위 문제는 “락 걸기"방법을 써도 문제가 해결되지 않는다.
위 코드가 쓰레드 안전성을 가지려면 v는 첫째 줄부터 셋째 줄까지 실행될 동안 계속 락이 걸린 채로
남아 있어야 한다.
Vector와 string
Chapter
2
항목 13 : 동적으로 할당한 배열보다는 vector와 string이 낫다.
프로그래머가 져야 할 세가지 부담을 vector와 string을 사용하면 부담을 없애준다.
Vector나 string이 메모리에서 소멸될 때 각 객체의 소멸자를 통해 해당 컨테이너 안의 요소를 모두 없애줌과
동시에 각 요소들이 점유하고 있던 메모리도 해제시킨다.
항목 14 : reserver는 필요없이 메모리가 재할당 되는 것을 막아준다..
STL 컨테이너에 대한 가장 신통한 특징 중 하나는 프로그래머 신경쓰지 않아도 추가되는 요소를 담을 수 있
다는 점이다.
Vector와 string에 있어서는 메모리 증가는 재할당이란 과정을 거쳐서 이루어진다.
할당-복사-소멸-해제. 여기서 상당한 비용이 들 수 있다.
Reserve멤버 함수는 사용할 메모리를 미리 할당해 둠으로써 재할당의 회수를 최소화시키고, 아울러
메모리 재할당과 반복자/포인터/참조자의 무효화로 인해 요구되는 비용 부담을 피해갈 수 있도록 한다.
1부터 1000까지의 값을 가지는 vector<int>를 만들고 싶다고 가정하자.
Reverse를 쓰지 않고 코드를 만들면
루프를 도는 동안 최소 2번에서 최대 10번의 재할당이 발생한다.
루프를 도는 동안 단 한번의 재할당도 일어나지 않는다.
항목 15 : 잊지 말자! String은 여러 가지 방식으로 구현되어 있다는 사실을…
String가 가지고 있는 기본적인 정보
문자열의 크기(size). 즉 string 객체에 담겨져 있는 실제 문자열의 길이(문자의 개수)
문자를 담아두는 메모리의 용량.
문자열의 값. 즉 문자열을 이루는 문자들
String가 가지고 있는 선택적인 정보
할당자의 사본.
참조 카운팅을 사용하는 string의 경우는 문자열 값에 대한 참조 카운트도 갖고 있다.
항목 16 : 기존의 C API에 vector와 string을 넘기는 방법을 알아두자
Vector 내의 데이터를 가리키는 포인터를 얻어낼 때 begin을 사용하면 안된다.
: begin의 반환 타입은 반복자이지, 포인터가 아니기 때문이다.
혹시나 begin을 사용하려면 v.begin() 대신에 &*v.begin() (&v[0]과 같은 형식)이라고 써라
이 방법은 string에는 통하지 않는다. 그 이유는?
1) String의 데이터 자체가 연속 메모리에 저장되도록 규정하고 있지 않다.
2) String의 내부 문자열 값이 널 문자로 끝나지 않는 것도 있다.
즉 문자열 s를 함수에 넘기려면
Void doSomething(const char *pString);
doSomething(s.c_str());를 사용한다.
항목 17 : 쓸데없이 남은 용량은 “바꿔치기(swap) 묘수”를 써서 없애 버리자.
Vector 내부의 크기를 줄여도 용량은 그대로 남아 있게 된다.
더 이상 필요없는 메모리를 벡터의 손아귀에서 빼내려면 “수축시켜 맞추기"를 해야 한다
Vector<Contestant>(contestants)
이 표현식은 contestants의 사본인 임시 벡터 객체를 만든다.
Vector의 복사 생성자가 contestants의 상수 참조자를 매개 변수로 받아 동작한다.
: 이 복사 생성자는 필요한 만큼의 메모리만을 할당한다. 이렇게 만들어지는 객체는 contestants와 달리
딱 맞는 용량이 잡혀있다. 그후 swap을 통해서 두 객체가 바뀌게 되고,
문장이 끝나면 할당된 임시 객체가 소멸된다.
항목 18 : vector<bool> 보기를 돌같이 하자
Vecotr<bool>은 STL콘테이너로서 두 가지가 잘못되어 있다.
1) STL콘테이너가 아니다
2) Bool을 담고 있지 않다
“c가 타입 T의 객체를 담는 컨테이너라면 c에서 operator[]가 지원되어야 한다.“
Operator[]를 써서 Container<T>에서 T객체 하나를 얻어낸다면 이 객체의 주소값을 가지고
객체의 포인터를 얻어낼 수 있다는 것.
하지만 vector<bool>은 그렇게 되지 않는다.
-> 공간을 줄이기 위해 bool을 압축시킨 데이터 표현 방식을 쓰기 때문에 컴파일에 실패
“vector”에 저장되는 “bool”을 하나의 비트로 나타내어 한 바이트로 여덟개의 bool을 담을 수 있게 구현한다
즉, vector<bool>은 비트필드를 써서 bool을 저장하고 있는 것처럼 흉내내는 것일 뿐이다
STL 연관 컨테이너
Chapter
3
3장 연관 컨테이너
이번 장에서는 동등 관계의 중요한 개념,
비교 함수를 사용할 때 잊지 말아야 할 제약 사항,
포인터의 연관 컨테이너에 대해 커스텀 비교함수를 쓸 때 좋은 점,
키의 상수성질(const를 붙일 때 생기는)에 대한 실체, 효율 향상 시키는 법 등에 대해 알아본다.
항목 19 : 상등관계(equaliy)와 동등 관계(equivalence)의 차이를 파악하자
- STL의 동작은 비교하는 것이 전부이다.
- Find 알고리즘은 상등성 / set::insert는 동등성을 가진다.(두 함수모두 같은 값인지를 알아내는 역할)
- Find는 operater== 을 사용한다. Set::insert는 operator<를 사용하는 차이가 있다.
- 하지만 operator==은 다음과 같은 경우에 내부가 다르더라도 같다고 판단한다
- 동등성은 다음과 같이 둘이 동등하다고 판단한다.
항목 19 : 상등관계(equaliy)와 동등 관계(equivalence)의 차이를 파악하자
- 표준 연관 컨테이너에 저장되는 데이터 요소는 정렬된 순서로 관리되기 대문에 모든 연관 컨테이
너에는 비교 함수가 반드시 필요하다. 동등성은 이 비교함수의 동작 원리에 따라 정해진다.
- 만일 비교 함수로 상등성원리를 이용했다면 값이 같은 것인지 알아내는 함수가 하나 더 필요 했을
것이다.
항목 20 : 포인터를 저장하는 연관 컨테이너에 대해서는 적합한 비교(비교 함수자)
타입을 정해주자.
- Set은 데이터를 정렬해서 관리한다.
- 하지만 포인터를 저장하는 컨테이너를 출력한다면 당연히 포인터가 나올 것이고, 포인터 포인터로
출력해서 값을 본다고 해도 원하는 순서대로 나올 확률은 n! 이다.(포인터 값에 의해 정렬되므로)
- 기본적인 비교자 함수는 less<string*> 이다.
항목 20 : 포인터를 저장하는 연고나 컨테이너에 대해서는 적합한 비교(비교 함수자)
타입을 정해주자.
- 우리가 원하는 순서로 값을 출력하려면 비교 함수자 클래스를 직접 만들어야 한다.
- 다음 클래스를 설계하고 이 클래스로 정렬한다.
- 하지만 이래서는 비교 함수자를 타입에 대해 매번 새로운 클래스로 만들어야 한다는 단점이 있다.
- 그렇기에 타입에 대한 템플릿 클래스를 만들어 두면 좋을 것이다.
- 스마트 포인터나, 반복자를 담는 경우에도 이 항목과의 똑같은 해결책으로 대응할 수 있다.
- 동등성과 상등성의 비교 할 때 문제점에서 출발한다.
- 이때 비교 함수자로 less_equal을 사용하고 있다.
- 이런…! 둘이 같은데 false 가 반환되고 있다!
- 정확히는 같은 값에 대해 true를 반환하는 비교 함수는 모두 이런 결과를 만든다.
- 이러한 함정을 피하기 위해서는 비교 함수의 반환 값은 어떤 값이 다른 값보다 정렬 순서에서 앞에
오는지 여부를 판단해야 한다. 비교 함수 쪽에서는 같은 값에 대해 반드시 false를 반환하게 하자.
항목 21 : 연관 컨테이너용 비교 함수는 같은 값에 대해 false를 반환해야한다.
- set 과 multiset은 내부에 저장되는 데이터 요소를 정렬해서 관리하며, 컨테이너의 정상적인 동작은
요소들이 정렬된 상태에서만 가능하다.
- map 과 multimap 컨테이너들의 키 값을 바꾸는 일은 그 자체가 불가능하다. key 부분이 const K
이기 때문에.(다만 const_cast로 는 가능하다)
- 하지만 이 장에서 말하고 싶은 것은 key 값을 바꾸지 말자는 것이다. 컨테이너가 엉망이 될 우려가
높기 때문이다.
- 중요한 사항은 "set/multiset에 저장된 요소의 정보를 바꿀 때에는 바꾼 후에도 반드시 모든 요소가
정렬되어 있도록 유지해야한다" 는 것이다.
항목 22 : set과 multiset에 저장된 데이터 요소에 대해 key를 바꾸는 일은 피하자
- 빠른 데이터 검색을 지원하는 자료구조가 필요하다" 라는 생각을 하게 되면 거의 연관 컨테이너를
떠올린다.
- 하지만 웬만큼 한다. 에서 그친다. 탐색 속도가 정말 중요한 경우라면 비표준의 해쉬 컨테이너를 사
용하는 것이 훨씬 좋다.(상수 시간의 빠른 탐색 속도를 주기 때문) set 이나 map 및 이들의 mulit버
전은 로그 시간의 탐색 속도 이다.
- 이때 그래도 로그 시간도 괜찮다. 고 하더라도 여전히 좋은 선택이 아닐 수도 있다. 제목 처럼 벡터
가 더 좋은 선택일 때가 있는 것이다.
항목 23 : 연관 컨테이너 대신 정렬된 vector를 쓰는 것이 좋을 때가 있다.
- 표준 연관 컨테이너는 이진 탐색 트리로 구현되어 있다. (탐색, 삽입, 삽입, 탐색, 삭제, 삽입, 탐색)
즉, 다음에 어떤 동작이 이루어질지 예측할 수 없는 구조이다.
- 보통 애플리케이션은 이렇게 혼란스럽게 자료구조를 사용하지 않는다.
1.데이터 셋업: 자료구조를 만들고 데이터 요소를 넣는다. (삽입/삭제가 대부분이고 탐색은 하지 않는
다.)
2.데이터 탐색: 셋업이 끝난 자료 구조를 사용하여 원하는 정보가 있는지 찾는다. (말 그래도 탐색만
한다.)
3.데이터 재구성: 자료 구조에 들어 있는 내용물을 바꾼다. 1의 동작과 흡사하다. 이후 다시 2단계로
진입한다.
- 이런 식의 자료구조를 사용하는 구조라면 벡터가 더 효율적일 가능성이 높다.(수행성능, 메모리 모
두) 하지만 이 벡터는 정렬 되어있어야 한다는 제약은 따른다.
항목 23 : 연관 컨테이너 대신 정렬된 vector를 쓰는 것이 좋을 때가 있다.
- 메모리 문제.
연관컨테이너일 경우 2진 탐색 트리를 사용하므로 객체 1개만 담아도 자식 노드2개가 필수적으로 붙
는 오버헤드가 발생한다.
벡터라고 한다면 여유공간을 잡아 놓는 오버헤드가 존재하긴 하지만, 그리 크지 않다.
정렬된 vector에 대한 데이터 저장은 연관 컨테이너에 대한 저장보다 메모리 소비량이 적을 가능성이
높고, 페이지 오류를 고려하면 정렬된 벡터를 사용한 이진 탐색은 연관컨테이너를 사용한 탐색보다 빠
를 가능성이 높다.
(물론 이경우 항상 정렬되어 있어야 한다는 단점이 있다. 이로 인해 벡터내부가 밀리고, 밀리던 와중에
이주까지 하게 된다면 많은 비용이 들어감에 틀림없지만 그렇다 하더라도 연관 컨테이너 보다 나을
수 있다.)
- 그렇기에 삽입&삭제 동작과 탐색 동작이 거의 섞이지 않는 경우에는 정렬된 벡터가 더 좋다.
항목 23 : 연관 컨테이너 대신 정렬된 vector를 쓰는 것이 좋을 때가 있다.
- map의 operator[ ] 연산자 함수는 유별난 특징이 있다. “추가 아니면 갱신”의 기능을 수행하도록 설
계 되어있다.
- 해당 맵에 키 k 가 있는지 먼저 점검한다. 그렇지 않다면 k와 v 가 페어로 묶여 맵에 추가된다. k가
맵에 있는 경우에는 k와 연관된 값이 v 로 갱신된다.
항목 24 : map::operator[ ]나 map::insert는 효율문제에 주의하여 선택하자.
- 추가 하는 경우에는 객체를 임시로 만드는데 필요한 기본생성자, 소멸자, 대입연산자 함수가 호출
되는 것이다. 이때는 operator[] 보다 원하는 값을 생성자 매개변수에 바로 넣는 것이 효율적일 수
있다.
- 위 코드는 기능적으로 아래 와 같다. 아래와 같이 호출 한번이 효율적일 것이다.
- 갱신의 경우에는 operator[ ]가 효율 적이다.
항목 24 : map::operator[ ]나 map::insert는 효율문제에 주의하여 선택하자.
- 해쉬 컨테이너는 연관 컨테이너이다. 새로운 점은 해쉬 함수를 지정해야 한다는 점이다.
- 이장에서는 딩컴웨어와, SGI 버전의 해쉬 함수를 알아보고 있다.
항목 25 : 현재는 표준이 아니지만 해쉬 컨테이너에 대해 충분히 대비해 두자.
반복자 iterator
Chapter
4
- iterator나 const_iterator를 증가시키면 해당 반복자는 현재 가리키고 있는 요소의 그 다음 요소를
가리킨다. 문제점은 insert 나 erase는 받아들이는 매개변수 타입으로 iterator를 요구한다는 점이다.
이것은 무조건 그냥 iterator이다.
- 그림에서 보게 되면 const_iterator를 iterator로 바꾸거나 const_reverse_iterator를 reverse_iterator
로 바꿀 수 없게 되어 있다. 왜냐하면 이 둘로는 STL 표준 컨테이너의 어떤 멤버 함수를 사용하는데
문제가 생기기 때문이다. (그렇기에 상수 반복자는 요소 삽입 위치나 삭제 위치를 지정하는데 쓸모
가 없다)
항목 26 : const_iterator, reverse_iterator, const_reverse_iterator도 좋지만 역시 쓸만
한 것은 iterator 이다.
- 하지만 상수반복자는 알고리즘에서 좋은 역할을 한다. 즉 오직 insert와 erase류의 함수에서 문제가
있을 뿐이다.
- 어떤 형태의 insert와 erase 멤버 함수는 무조건 iterator만을 넘겨야 한다.
- const_iterator를 iterator로 암묵적 변환하지 못한다.
- reverse_iterator를 iterator로 변환할 수 있으나, 변환한 후에 약간의 조정이 필요하다.
(base() 함수를 사용하고, 왜 이렇게 변환하는지는 항목28에서 알아본다)
- 요점은 이 타입 저 타입의 반복자를 석어쓰면 문제가 된다. 최대한 그냥 iterator를 사용하는 것이
좋겠다.
항목 26 : const_iterator, reverse_iterator, const_reverse_iterator도 좋지만 역시 쓸만
한 것은 iterator 이다.
- 항목26에서 삽입과 삭제를 하고싶은데 이미 const_iterator를 사용하고 있다면 어찌해야 할까. 이런
경우에는 직접 손을 써야한다.
- 캐스팅일까? p190에서의 예와 같이 const_cast를 사용한다 하더라도 에러가 나타난다. (안돼는 이
유는 서로 다른 클래스 이기 때문이다.) 즉 코드의 수정이 필요하다.
항목 27 : const_iterator를 iterator로 바꾸는 데에는 distance와 advance를 사용하자.
- iterator 타입을 데큐의 컨테이너의 begin()으로 초기화 한다. 그후 advance함수와 distance 함수를
사용하여 const_iterator위치로 iterator i 를 옮긴다.
- 이때 두 함수는 iterator의 인스턴스로 만들어진 템플릿이다.
- advance는 어떤 반복자를 지정된 거리만큼 이동(전진)시킨다.
- distance는 같은 컨테이너를 가리키고 있는 두 반복자 사이의 거리를 알려준다.
- 이제 i 와 ci 가 같은 컨테이너를 가리키고 있는 한 advance(i, distance(i,ci))표현식이 실행 되었을 때
i와 ci는 컨테이너의 같은 위치를 가리킨다.
- 하지만 이때 distance의 매개변수 부분이 서로 다른 타입이기때문에 아직 컴파일이 되지 않는다. 이
때에는 타입에 관련된 모호함을 없애 주어야한다.
- typedef deque<int> intDeque;
- typedef intDeque::iterator Iter;
- typedef intDeque::const_iterator ConstIter;
- advance(i, distance<ConstIter>(i, ci)); //이와 같이 모호함을 없애 준다.
항목 27 : const_iterator를 iterator로 바꾸는 데에는 distance와 advance를 사용하자.
- base 멤버함수를 호출하면 reverse_iterator로 iterator를 얻을 수 있다고 했다. 하지만 정확히 그런
것은 아니다.
- 실제로 ri 와 i 가 가리키는 부분이 다르다!
항목 28 : reverse_iterator에 대응되는 기점 반복자(base iterator)를 사용하는 방법을
정확하게 이해하자.
- 이러한 이유가 있다. ri를 i 로 바꾸는 이유는 ri로 insert와 erase를 할 수 없기 때문에 바꿔주는 것이
고 ri 일 때는 오른쪽에서 왼쪽으로 컨테이너 요소를 이동하지만 삽입은 반복자가 가리키는 위치의
바로 앞에서 이루어지기 때문이다.
- 즉 요소 삽입이 목적이라면 ri.base()는 ri에 정확히 대응되는 반복자이다.
- 그렇다면 삭제는 어떨까? 삭제는 기점 반복자(base()) i 를 사용할 수 없다. i가 가리키는 앞에 있는
요소를 삭제해야 맞는 것이다.
- 즉 reverse_iterator인 ri 로 지정된 위치에 있는 요소를 삭제하려면 ri.base()의 앞에 있는 위치에서
삭제를 수행해야한다. 요소 삭제가 목적이라면 ri 와 ri.base()는 동등하지 않으며 ri에 대응되는
iterator가 아니다.
- 그렇기에 다음과 같은 아이디어를 사용한다. v.erase((++ri).base());
항목 28 : reverse_iterator에 대응되는 기점 반복자(base iterator)를 사용하는 방법을
정확하게 이해하자.
- 어떤 텍스트 파일을 읽을때, istream_iterator가 사용하는 operator>> 함수는 서식화 입력을 수행한
다. (여러 뒷처리를 알아서 해준다)
- 그렇기에 서식화 입력을 할 대는 매우 소중하고 값진 것이지만, 그냥 입력 스트림으로부터 다음 문
자를 뽑아내는 일만 하고싶다면 비용손실이 많다.
- 이때 istreambur_iterator를 사용해보자. 이 반복자는 스트림 자체의 버퍼를 직접 건드려서 다음 문
자들을 바로 읽어 들인다. 심지어 이 것은 공백문자도 무시하지 않는다. 또한 속도도 상대적으로 빠
르다.
항목 29 : 문자 단위의 입력에는 istreambuf_iterator의 사용도 적절하다.

More Related Content

What's hot

[Windows via c/c++] 4장 프로세스
[Windows via c/c++] 4장 프로세스[Windows via c/c++] 4장 프로세스
[Windows via c/c++] 4장 프로세스종빈 오
 
이펙티브 C++ 공부
이펙티브 C++ 공부이펙티브 C++ 공부
이펙티브 C++ 공부quxn6
 
Windows via c++ part 1
Windows via c++ part 1Windows via c++ part 1
Windows via c++ part 1Shin heemin
 
모어이펙티브 C++ 3,4장 예외, 효율 스터디
모어이펙티브 C++ 3,4장 예외, 효율 스터디모어이펙티브 C++ 3,4장 예외, 효율 스터디
모어이펙티브 C++ 3,4장 예외, 효율 스터디quxn6
 
LockFree Algorithm
LockFree AlgorithmLockFree Algorithm
LockFree AlgorithmMerry Merry
 
이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디quxn6
 
Python_numpy_pandas_matplotlib 이해하기_20160815
Python_numpy_pandas_matplotlib 이해하기_20160815Python_numpy_pandas_matplotlib 이해하기_20160815
Python_numpy_pandas_matplotlib 이해하기_20160815Yong Joon Moon
 
05_STL컨테이너정리
05_STL컨테이너정리05_STL컨테이너정리
05_STL컨테이너정리noerror
 
이펙티브 C++ 스터디
이펙티브 C++ 스터디이펙티브 C++ 스터디
이펙티브 C++ 스터디quxn6
 
동기화, 스케줄링
동기화, 스케줄링동기화, 스케줄링
동기화, 스케줄링xxbdxx
 
windows via c++ Ch 5. Job
windows via c++ Ch 5. Jobwindows via c++ Ch 5. Job
windows via c++ Ch 5. JobHyosung Jeon
 
PINTOS Operating system homework
PINTOS Operating system homeworkPINTOS Operating system homework
PINTOS Operating system homeworkGichan Lee
 
Effective c++chapter8
Effective c++chapter8Effective c++chapter8
Effective c++chapter8성연 김
 
Windows via c/c++ 스터디9장
Windows via c/c++ 스터디9장Windows via c/c++ 스터디9장
Windows via c/c++ 스터디9장HolyTak
 
More effective c++ chapter1,2
More effective c++ chapter1,2More effective c++ chapter1,2
More effective c++ chapter1,2문익 장
 
Effective c++chapter4
Effective c++chapter4Effective c++chapter4
Effective c++chapter4성연 김
 
Effective c++chapter1 and2
Effective c++chapter1 and2Effective c++chapter1 and2
Effective c++chapter1 and2성연 김
 

What's hot (20)

[Windows via c/c++] 4장 프로세스
[Windows via c/c++] 4장 프로세스[Windows via c/c++] 4장 프로세스
[Windows via c/c++] 4장 프로세스
 
이펙티브 C++ 공부
이펙티브 C++ 공부이펙티브 C++ 공부
이펙티브 C++ 공부
 
Windows via c++ part 1
Windows via c++ part 1Windows via c++ part 1
Windows via c++ part 1
 
모어이펙티브 C++ 3,4장 예외, 효율 스터디
모어이펙티브 C++ 3,4장 예외, 효율 스터디모어이펙티브 C++ 3,4장 예외, 효율 스터디
모어이펙티브 C++ 3,4장 예외, 효율 스터디
 
LockFree Algorithm
LockFree AlgorithmLockFree Algorithm
LockFree Algorithm
 
이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디이펙티브 C++ 5,6 장 스터디
이펙티브 C++ 5,6 장 스터디
 
Python_numpy_pandas_matplotlib 이해하기_20160815
Python_numpy_pandas_matplotlib 이해하기_20160815Python_numpy_pandas_matplotlib 이해하기_20160815
Python_numpy_pandas_matplotlib 이해하기_20160815
 
05_STL컨테이너정리
05_STL컨테이너정리05_STL컨테이너정리
05_STL컨테이너정리
 
이펙티브 C++ 스터디
이펙티브 C++ 스터디이펙티브 C++ 스터디
이펙티브 C++ 스터디
 
동기화, 스케줄링
동기화, 스케줄링동기화, 스케줄링
동기화, 스케줄링
 
120114 windows viacpp_03
120114 windows viacpp_03120114 windows viacpp_03
120114 windows viacpp_03
 
Gpg1
Gpg1Gpg1
Gpg1
 
windows via c++ Ch 5. Job
windows via c++ Ch 5. Jobwindows via c++ Ch 5. Job
windows via c++ Ch 5. Job
 
Jvm
JvmJvm
Jvm
 
PINTOS Operating system homework
PINTOS Operating system homeworkPINTOS Operating system homework
PINTOS Operating system homework
 
Effective c++chapter8
Effective c++chapter8Effective c++chapter8
Effective c++chapter8
 
Windows via c/c++ 스터디9장
Windows via c/c++ 스터디9장Windows via c/c++ 스터디9장
Windows via c/c++ 스터디9장
 
More effective c++ chapter1,2
More effective c++ chapter1,2More effective c++ chapter1,2
More effective c++ chapter1,2
 
Effective c++chapter4
Effective c++chapter4Effective c++chapter4
Effective c++chapter4
 
Effective c++chapter1 and2
Effective c++chapter1 and2Effective c++chapter1 and2
Effective c++chapter1 and2
 

Similar to Effective STL 1~4장 정리

C++ Advanced 강의 4주차
 C++ Advanced 강의 4주차 C++ Advanced 강의 4주차
C++ Advanced 강의 4주차HyunJoon Park
 
2014-15 Intermediate C++ Study #7
2014-15 Intermediate C++ Study #72014-15 Intermediate C++ Study #7
2014-15 Intermediate C++ Study #7Chris Ohk
 
C++ Template/STL study
C++ Template/STL studyC++ Template/STL study
C++ Template/STL studySeo Dong-yu
 
effective c++ chapter 3~4 정리
effective c++ chapter 3~4 정리effective c++ chapter 3~4 정리
effective c++ chapter 3~4 정리Injae Lee
 
Effective c++ Chapter1,2
Effective c++ Chapter1,2Effective c++ Chapter1,2
Effective c++ Chapter1,2문익 장
 
Mec++ chapter3,4
Mec++ chapter3,4Mec++ chapter3,4
Mec++ chapter3,4문익 장
 
Effective c++ 1~8장
Effective c++ 1~8장 Effective c++ 1~8장
Effective c++ 1~8장 Shin heemin
 
Effective cpp
Effective cppEffective cpp
Effective cppTonyCms
 
Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4연우 김
 
이펙티브 C++ (7~9)
이펙티브 C++ (7~9)이펙티브 C++ (7~9)
이펙티브 C++ (7~9)익성 조
 
Effective c++ 1,2
Effective c++ 1,2Effective c++ 1,2
Effective c++ 1,2세빈 정
 
연산자 오버로딩
연산자 오버로딩연산자 오버로딩
연산자 오버로딩수빈 박
 
Effective C++ Chapter 1 Summary
Effective C++ Chapter 1 SummaryEffective C++ Chapter 1 Summary
Effective C++ Chapter 1 SummarySeungYeonChoi10
 
모어 이펙티브 c++ 5장 스터디
모어 이펙티브 c++ 5장 스터디모어 이펙티브 c++ 5장 스터디
모어 이펙티브 c++ 5장 스터디quxn6
 
Effective C++ Chaper 1
Effective C++ Chaper 1Effective C++ Chaper 1
Effective C++ Chaper 1연우 김
 
More effective c++ 2
More effective c++ 2More effective c++ 2
More effective c++ 2현찬 양
 
Effective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshinEffective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshinDong Chan Shin
 
디자인패턴 1~13
디자인패턴 1~13디자인패턴 1~13
디자인패턴 1~13Shin heemin
 

Similar to Effective STL 1~4장 정리 (20)

C++ Advanced 강의 4주차
 C++ Advanced 강의 4주차 C++ Advanced 강의 4주차
C++ Advanced 강의 4주차
 
2014-15 Intermediate C++ Study #7
2014-15 Intermediate C++ Study #72014-15 Intermediate C++ Study #7
2014-15 Intermediate C++ Study #7
 
C++ Template/STL study
C++ Template/STL studyC++ Template/STL study
C++ Template/STL study
 
effective c++ chapter 3~4 정리
effective c++ chapter 3~4 정리effective c++ chapter 3~4 정리
effective c++ chapter 3~4 정리
 
Effective c++ Chapter1,2
Effective c++ Chapter1,2Effective c++ Chapter1,2
Effective c++ Chapter1,2
 
Mec++ chapter3,4
Mec++ chapter3,4Mec++ chapter3,4
Mec++ chapter3,4
 
Effective c++ 1~8장
Effective c++ 1~8장 Effective c++ 1~8장
Effective c++ 1~8장
 
Effective cpp
Effective cppEffective cpp
Effective cpp
 
Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4Effective c++ 정리 chapter 4
Effective c++ 정리 chapter 4
 
이펙티브 C++ (7~9)
이펙티브 C++ (7~9)이펙티브 C++ (7~9)
이펙티브 C++ (7~9)
 
Effective c++ 1,2
Effective c++ 1,2Effective c++ 1,2
Effective c++ 1,2
 
연산자 오버로딩
연산자 오버로딩연산자 오버로딩
연산자 오버로딩
 
Stl 컨테이너
Stl 컨테이너Stl 컨테이너
Stl 컨테이너
 
Effective C++ Chapter 1 Summary
Effective C++ Chapter 1 SummaryEffective C++ Chapter 1 Summary
Effective C++ Chapter 1 Summary
 
모어 이펙티브 c++ 5장 스터디
모어 이펙티브 c++ 5장 스터디모어 이펙티브 c++ 5장 스터디
모어 이펙티브 c++ 5장 스터디
 
STL.doc
STL.docSTL.doc
STL.doc
 
Effective C++ Chaper 1
Effective C++ Chaper 1Effective C++ Chaper 1
Effective C++ Chaper 1
 
More effective c++ 2
More effective c++ 2More effective c++ 2
More effective c++ 2
 
Effective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshinEffective c++ chapter1 2_dcshin
Effective c++ chapter1 2_dcshin
 
디자인패턴 1~13
디자인패턴 1~13디자인패턴 1~13
디자인패턴 1~13
 

Effective STL 1~4장 정리

  • 3. 항목 1 : 적재적소에 알맞은 컨테이너를 사용하자 표준 STL 시퀀스 컨테이너 : vector, string, deque, list. 표준 STL 연관 컨테이너 : set, multiset, map, multimap. 비표준 시퀀스 컨테이너 : slist, rope 비표준 연관 컨테이너 : hash_set, hash_multiset, hash_map, hash_multimap String 대신 사용되는 vector<char> 표준 연관 컨테이너 대신 사용되는 vector STL에 속하지 않는 표준 컨테이너 : 배열, bitset, valarray, stack, queue, priority_queue Vector, list, deque의 선택 기준 Vector : 기본적으로 사용되는 시퀀스 List : 시퀀스의 중간에 빈번한 삽입, 삭제가 수행될 필요가 있을때 사용 Deque는 대부분의 삽입과 삭제가 시퀀스의 앞과 끝에서 일어나는 경우
  • 4. STL컨테이너는 연속 메모리 컨테이너와 노드 기반 컨테이너로 나눌 수 있다. 연속 메모리 컨테이너 – vector, string, deque : 동적 할당된 하나 이상의 메모리 단위에다가 데이터 요소를 저장해 두는 컨테이너 새 요소가 삽입되거나 이미 있던 요소가 지워지면 재배열이 일어나 수행 성능에 발목을 잡는다. 노드 기반 컨테이너 : 동적 할당된 하나의 메모리 단위에다가 하나의 요소만을 저장 컨테이너 요소를 삽입 혹은 삭제 했을때 노드의 포인터만이 영향을 받지, 노드의 내용은 그대로이다. 연결 리스트를 나타내는 컨테이너, 즉 list나 slist가 노드 기반이고, 표준 연관 콘테이너 모두가 노드 기반
  • 5. 항목 2 : “컨테이너에 독립적인 코드"라는 환상을 조심하자. Vector를 쓰는 부분을 만들면서 언제든지 vector대신 deque나 list를 쓸 수 있는 여지를 남겨 놓는 행위. 즉 컨테이너에 독립적인 코드를 작성하려고 기를 쓴다는것은 잘못된 이해에서 나오는 행동 반복자, 포인터, 참조자를 무효화시키는 방식이 컨테이너마다 다르다!
  • 6. 경우에 따라 수시로 컨테이너 타입을 바꿀 수밖에 없는 입장이라면, 변경을 조금 용이하게 해주는 방법이 있다. “캡슐화”
  • 7. 항목 3 : 복사는 컨테이너 안의 객체에 맞게 비용은 최소화하며, 동작은 정확하게 어떤 컨테이너에 어떤 객체를 밀어 넣을때 컨테이너에 들어가는 것은 그 객체의 복사본이다. 컨테이너에서 어떤 객체를 뽑아낼 때는 컨테이너에 들어 있던 객체의 참조자를 얻어 원하는 객체에 복사한다. 복사되어 들어가고, 복사되어 나오는것이 STL의 방식 객체의 복사는 해당 클래스의 복사용 멤버 함수를 사용하여 이루어지는데, 특히 복사 생성자와 복사 대입 연산자가 사용된다.
  • 8. STL에서 복사는 거의 모든 순간에 일어난다. 복사에 드는 비용이 큰 객체를 컨테이너에 넣을 때에는 그냥 단순히 객체를 컨테이너에 넣기만 하면 수행 성능의 병목현상을 일으키고 만다. 컨테이너에 더 많은 객체가 들어갈수록 시스템에 몰아치는 메모리 사용량과 CPU 사이클의 폭풍은 더욱 세진다. Slice : 상속된 객체의 경우 복사 동작 중에 데이터가 없어지는 경우 기본 클래스 객체의 컨테이너를 만들어놓고 여기다가 파생 클래스 객체를 넣으면 Slice가 발생
  • 9. 포인터의 컨테이너 : 속도 빠르고, 정확하고, Slice문제에도 끄덕없는 복사를 쉽게 하는 방법 - 포인터의 컨테이너를 사용해서 복사를 하되 골치 거리를 피하는데에는 스마트 포인터가 괜찮은 방법 STL은 “불필요한” 복사를 피하도록 설계되어 있다. : 이들 중 몇 개만 필요하든지, 아니면 기본으로 만들어진 값을 어딘가에서 읽어온 다른 값으로 덮어쓰고 싶다던지 해도 무조건 maxNumWidgets개의 Wiget에서 기본 생성자가 호출된다. 이런 배열 대신 vector를 사용하면 필요할 때마다 크기가 자라도록 할 수 있다.
  • 10. 항목 4 : size()의 결과를 0과 비교할 생각이라면 차라리 empty를 호출하자. 두 코드는 본질적으로 똑같은 코드이다. 하지만 empty가 들어간 코드를 쓰는것이 더 좋다. Empty는 모든 표준 콘테이너에 대해 상수 시간에 수행되지만, 몇몇 제품에서 구현된 list 클래스에서 size가 선형 시간에 수행되기 때문이다.
  • 11. 항목 5 : 단일 요소를 단위로 동작하는 멤버 함수보다 요소의 범위를 단위로 동작하 는 멤버 함수가 낫다. 단일 요소 멤버 함수보다 범위 멤버 함수가 더 좋은 이유 1. 범위 멤버 함수를 사용한 코드가 대개 짧다. 2. 범위 멤버 함수는 훨씬 명확하고 간결한 의미를 전달한다. 단일 요소 멤버 함수에서는 메모리 할당자의 요구도 많고 객체 복사도 빈번하며 불필요한 연산도 자주 일어난다. 같은 일을 한다고 볼 때 범위 멤버 함수는 이보다 효율적이다.
  • 12. 범위 생성 : 모든 표준 컨테이너는 다음과 같은 형태의 생성자를 지원하고 있습니다. 범위 삽입 : 모든 표준 컨테이너는 다음과 같은 형태의 insert를 지원하고 있다. 범위 삭제 : 표준 컨테이너에서 범위 버전의 erase를 제공하고 있지만, 반환 타입은 시퀀스 컨테이너와 연관 컨테이너에 대해서 각각 다르다.
  • 13. 항목 6 : C++ 컴파일러의 어이 없는 분석 결과를 조심하자. Int 데이터가 들어 있는 파일이 있고, 이 파일에 있는 int를 list에 복사한다고 할때 위 코드는 리스트 객체가 아니라 data라는 이름의 함수를 선언한 것이다! 그러므로 틀린 코드
  • 14. 함수의 매개변수에 괄호를 넣음으로써 해결할 수 있다. Data라는 리스트 객체가 제대로 생성된다. Istream_iterator가 매개 변수로 들어가고, 범위 생성자가 호출된다. 하지만 , 모든 컴파일러가 이렇지는 않다.
  • 15. 익명 객체 선언을 istream_iterator에 쓰지 말고, 각 반복자 객체의 이름을 만들어 주는 것!
  • 16. 항목 7 : new로 생성한 포인터의 컨테이너를 사용할 때에는 컨테이너가 소멸되기 전에 포인터를 delete한흔 일을 잊지 말자. STL에서는 삽입과 삭제 떄에 필요한 어떤 메모리 관리도 알아서 척척 해준다. 객체가 몇 개나 들어있고, 최대 몇 개까지 담을 수 있는가에 대한 데이터도 가지고 있고, 자신이 메모리에서 인생을 마칠 때 그 때까지 가지고 있던 객체도 자동으로 없애준다. 하지만 new로 할당된 포인터를 컨테이너에 담는 경우에는 이야기가 다르다. 포인터의 컨테이너는 자신이 소멸될 때 각 요소 자체를 없애 주기는 하지만, 포인터의 소멸자가 아무 일도 하지 않는다는 것이다. 포인터에 대해 delete를 하지 않기 때문이다.
  • 17.
  • 18. 위 코드도 완벽한 코드가 아닌 무난한 코드이다! For_each가 하는 일을 for 루프로 대신하고 있는데, 이것은 for_each만큼 명확하지 못하다. 또한 예외 안전성을 갖추고 있지 않다. Vwp에 포인터가 들어있고, vwp 내부에 돌면서 delete를 수행할 직전에 예외가 발생하면, 불쌍한 메모리는 다시 샌다. For_each와 비슷하게 만든 for 루프를 for_each로 바꾸려면 delete를 수행하는 부분을 함수 객체로 바꾸어야 한다.
  • 19. 위 함수를 사용하면 추적이 힘든 버그를 일으킬 수 있다. 가령, string을 상속한 클래스를 선언했다고 사정할 시, String은 가상 소멸자를 가지고 있지 않아서 public으로 상속한 클래스는 C++의 패륜아가 된다! DeleteOBject::operator()에 넘겨지는 포인터의 타입을 컴파일러가 직접 판단할 수 있도록 한다.
  • 20. 제대로 잘 동작하면서 타입 안전성도 갖추고 있는 코드이다.
  • 21. 하지만 아직도 예외 안전성이 없다!! 포인터 컨테이너 대신에 스마트 포인터의 컨테이너를 사용 하는 것 !
  • 22. 항목 8 : auto_ptr의 컨테이너는 절대로 만들지 말자.
  • 23. 항목 9 : 데이터를 삭제할 때에도 조심스럽게 선택할 것이 많다. C에 들어있는 정수 중에 1963이라는 값을 가진 것은 모두 지우고 싶다! 연속 메모리 컨테이너 일때 :
  • 24. 표준 연관 컨테이너 일때는 rmove라는 이름을 가진 어떤 것도 소용이 없다. 삭제가 효율적으로 될 뿐만 아니라, 로그 시간만큼만 걸린다 또한 연관 컨테이너의 erase멤버 함수는 상등성이 아닌 동등성에 기반하고 있다는 이점!
  • 25. 표준 연관 컨테이너의 경우 이 문제가 위처럼 쉽게 해결되지 않는다. 2가지의 방법이 있다. 1. 코딩하기 쉽지만, 비효율적 2. 코딩하기 어렵지만, 효율적
  • 26. 1. 코딩하기 쉽지만, 비효율적인 방법 : remove_copy_if를 사용하여 컨테이너에서 없애지 말아야 할 값들을 새 컨테이너에 복사했다가 새 컨테이너와 원래의 컨테이너를 swap하는 것 : 복사에 걸리는 비용이 비싸다.
  • 27. 연관 컨테이너는 remove_if와 비슷한 멤버함수를 제공하지 않기 때문에, c내의 요소를 처음부터 횡단하는 루프를 넣어, 요소를 직접 검사해서 지워야 한다.
  • 29. 항목 10, 11. 이해가 잘 되지 않습니다…
  • 30. 항목 12 : STL 컨테이너의 쓰레드 안전성에 대한 기대는 현실에 맞추어 가지자. 많은 프로그래머들의 바람은 STL이 완벽하게 쓰레드 안전성을 가지고 있는 것이다. 자신들이 짊어져야 하는 동시성 제어의 부담에서 벗어날 수 있다고 생각한다. 컨테이너의 완벽한 쓰레드 안전성을 구현하려면 라이브러리 쪽에서 어떤 것을 해야 할지 정리해 보자.
  • 31. 다중 쓰레드 환경에서는 첫째 줄의 실행이 끝난 직후에 다른 쓰레드 하나가 v의 값을 바꾸는 일이 생김 둘째 줄에서 first5를 v.end()와 비교하는 것은 쓸모가 없다. -> v의 값은 첫째 줄의 마지막에 있을 때와 다르기 때문
  • 32. 위 문제는 “락 걸기"방법을 써도 문제가 해결되지 않는다. 위 코드가 쓰레드 안전성을 가지려면 v는 첫째 줄부터 셋째 줄까지 실행될 동안 계속 락이 걸린 채로 남아 있어야 한다.
  • 34. 항목 13 : 동적으로 할당한 배열보다는 vector와 string이 낫다. 프로그래머가 져야 할 세가지 부담을 vector와 string을 사용하면 부담을 없애준다. Vector나 string이 메모리에서 소멸될 때 각 객체의 소멸자를 통해 해당 컨테이너 안의 요소를 모두 없애줌과 동시에 각 요소들이 점유하고 있던 메모리도 해제시킨다.
  • 35. 항목 14 : reserver는 필요없이 메모리가 재할당 되는 것을 막아준다.. STL 컨테이너에 대한 가장 신통한 특징 중 하나는 프로그래머 신경쓰지 않아도 추가되는 요소를 담을 수 있 다는 점이다. Vector와 string에 있어서는 메모리 증가는 재할당이란 과정을 거쳐서 이루어진다. 할당-복사-소멸-해제. 여기서 상당한 비용이 들 수 있다.
  • 36. Reserve멤버 함수는 사용할 메모리를 미리 할당해 둠으로써 재할당의 회수를 최소화시키고, 아울러 메모리 재할당과 반복자/포인터/참조자의 무효화로 인해 요구되는 비용 부담을 피해갈 수 있도록 한다. 1부터 1000까지의 값을 가지는 vector<int>를 만들고 싶다고 가정하자. Reverse를 쓰지 않고 코드를 만들면 루프를 도는 동안 최소 2번에서 최대 10번의 재할당이 발생한다. 루프를 도는 동안 단 한번의 재할당도 일어나지 않는다.
  • 37. 항목 15 : 잊지 말자! String은 여러 가지 방식으로 구현되어 있다는 사실을… String가 가지고 있는 기본적인 정보 문자열의 크기(size). 즉 string 객체에 담겨져 있는 실제 문자열의 길이(문자의 개수) 문자를 담아두는 메모리의 용량. 문자열의 값. 즉 문자열을 이루는 문자들 String가 가지고 있는 선택적인 정보 할당자의 사본. 참조 카운팅을 사용하는 string의 경우는 문자열 값에 대한 참조 카운트도 갖고 있다.
  • 38. 항목 16 : 기존의 C API에 vector와 string을 넘기는 방법을 알아두자 Vector 내의 데이터를 가리키는 포인터를 얻어낼 때 begin을 사용하면 안된다. : begin의 반환 타입은 반복자이지, 포인터가 아니기 때문이다. 혹시나 begin을 사용하려면 v.begin() 대신에 &*v.begin() (&v[0]과 같은 형식)이라고 써라 이 방법은 string에는 통하지 않는다. 그 이유는? 1) String의 데이터 자체가 연속 메모리에 저장되도록 규정하고 있지 않다. 2) String의 내부 문자열 값이 널 문자로 끝나지 않는 것도 있다. 즉 문자열 s를 함수에 넘기려면 Void doSomething(const char *pString); doSomething(s.c_str());를 사용한다.
  • 39. 항목 17 : 쓸데없이 남은 용량은 “바꿔치기(swap) 묘수”를 써서 없애 버리자. Vector 내부의 크기를 줄여도 용량은 그대로 남아 있게 된다. 더 이상 필요없는 메모리를 벡터의 손아귀에서 빼내려면 “수축시켜 맞추기"를 해야 한다 Vector<Contestant>(contestants) 이 표현식은 contestants의 사본인 임시 벡터 객체를 만든다. Vector의 복사 생성자가 contestants의 상수 참조자를 매개 변수로 받아 동작한다. : 이 복사 생성자는 필요한 만큼의 메모리만을 할당한다. 이렇게 만들어지는 객체는 contestants와 달리 딱 맞는 용량이 잡혀있다. 그후 swap을 통해서 두 객체가 바뀌게 되고, 문장이 끝나면 할당된 임시 객체가 소멸된다.
  • 40. 항목 18 : vector<bool> 보기를 돌같이 하자 Vecotr<bool>은 STL콘테이너로서 두 가지가 잘못되어 있다. 1) STL콘테이너가 아니다 2) Bool을 담고 있지 않다 “c가 타입 T의 객체를 담는 컨테이너라면 c에서 operator[]가 지원되어야 한다.“ Operator[]를 써서 Container<T>에서 T객체 하나를 얻어낸다면 이 객체의 주소값을 가지고 객체의 포인터를 얻어낼 수 있다는 것. 하지만 vector<bool>은 그렇게 되지 않는다. -> 공간을 줄이기 위해 bool을 압축시킨 데이터 표현 방식을 쓰기 때문에 컴파일에 실패 “vector”에 저장되는 “bool”을 하나의 비트로 나타내어 한 바이트로 여덟개의 bool을 담을 수 있게 구현한다 즉, vector<bool>은 비트필드를 써서 bool을 저장하고 있는 것처럼 흉내내는 것일 뿐이다
  • 42. 3장 연관 컨테이너 이번 장에서는 동등 관계의 중요한 개념, 비교 함수를 사용할 때 잊지 말아야 할 제약 사항, 포인터의 연관 컨테이너에 대해 커스텀 비교함수를 쓸 때 좋은 점, 키의 상수성질(const를 붙일 때 생기는)에 대한 실체, 효율 향상 시키는 법 등에 대해 알아본다.
  • 43. 항목 19 : 상등관계(equaliy)와 동등 관계(equivalence)의 차이를 파악하자 - STL의 동작은 비교하는 것이 전부이다. - Find 알고리즘은 상등성 / set::insert는 동등성을 가진다.(두 함수모두 같은 값인지를 알아내는 역할) - Find는 operater== 을 사용한다. Set::insert는 operator<를 사용하는 차이가 있다. - 하지만 operator==은 다음과 같은 경우에 내부가 다르더라도 같다고 판단한다 - 동등성은 다음과 같이 둘이 동등하다고 판단한다.
  • 44. 항목 19 : 상등관계(equaliy)와 동등 관계(equivalence)의 차이를 파악하자 - 표준 연관 컨테이너에 저장되는 데이터 요소는 정렬된 순서로 관리되기 대문에 모든 연관 컨테이 너에는 비교 함수가 반드시 필요하다. 동등성은 이 비교함수의 동작 원리에 따라 정해진다. - 만일 비교 함수로 상등성원리를 이용했다면 값이 같은 것인지 알아내는 함수가 하나 더 필요 했을 것이다.
  • 45. 항목 20 : 포인터를 저장하는 연관 컨테이너에 대해서는 적합한 비교(비교 함수자) 타입을 정해주자. - Set은 데이터를 정렬해서 관리한다. - 하지만 포인터를 저장하는 컨테이너를 출력한다면 당연히 포인터가 나올 것이고, 포인터 포인터로 출력해서 값을 본다고 해도 원하는 순서대로 나올 확률은 n! 이다.(포인터 값에 의해 정렬되므로) - 기본적인 비교자 함수는 less<string*> 이다.
  • 46. 항목 20 : 포인터를 저장하는 연고나 컨테이너에 대해서는 적합한 비교(비교 함수자) 타입을 정해주자. - 우리가 원하는 순서로 값을 출력하려면 비교 함수자 클래스를 직접 만들어야 한다. - 다음 클래스를 설계하고 이 클래스로 정렬한다. - 하지만 이래서는 비교 함수자를 타입에 대해 매번 새로운 클래스로 만들어야 한다는 단점이 있다. - 그렇기에 타입에 대한 템플릿 클래스를 만들어 두면 좋을 것이다. - 스마트 포인터나, 반복자를 담는 경우에도 이 항목과의 똑같은 해결책으로 대응할 수 있다.
  • 47. - 동등성과 상등성의 비교 할 때 문제점에서 출발한다. - 이때 비교 함수자로 less_equal을 사용하고 있다. - 이런…! 둘이 같은데 false 가 반환되고 있다! - 정확히는 같은 값에 대해 true를 반환하는 비교 함수는 모두 이런 결과를 만든다. - 이러한 함정을 피하기 위해서는 비교 함수의 반환 값은 어떤 값이 다른 값보다 정렬 순서에서 앞에 오는지 여부를 판단해야 한다. 비교 함수 쪽에서는 같은 값에 대해 반드시 false를 반환하게 하자. 항목 21 : 연관 컨테이너용 비교 함수는 같은 값에 대해 false를 반환해야한다.
  • 48. - set 과 multiset은 내부에 저장되는 데이터 요소를 정렬해서 관리하며, 컨테이너의 정상적인 동작은 요소들이 정렬된 상태에서만 가능하다. - map 과 multimap 컨테이너들의 키 값을 바꾸는 일은 그 자체가 불가능하다. key 부분이 const K 이기 때문에.(다만 const_cast로 는 가능하다) - 하지만 이 장에서 말하고 싶은 것은 key 값을 바꾸지 말자는 것이다. 컨테이너가 엉망이 될 우려가 높기 때문이다. - 중요한 사항은 "set/multiset에 저장된 요소의 정보를 바꿀 때에는 바꾼 후에도 반드시 모든 요소가 정렬되어 있도록 유지해야한다" 는 것이다. 항목 22 : set과 multiset에 저장된 데이터 요소에 대해 key를 바꾸는 일은 피하자
  • 49. - 빠른 데이터 검색을 지원하는 자료구조가 필요하다" 라는 생각을 하게 되면 거의 연관 컨테이너를 떠올린다. - 하지만 웬만큼 한다. 에서 그친다. 탐색 속도가 정말 중요한 경우라면 비표준의 해쉬 컨테이너를 사 용하는 것이 훨씬 좋다.(상수 시간의 빠른 탐색 속도를 주기 때문) set 이나 map 및 이들의 mulit버 전은 로그 시간의 탐색 속도 이다. - 이때 그래도 로그 시간도 괜찮다. 고 하더라도 여전히 좋은 선택이 아닐 수도 있다. 제목 처럼 벡터 가 더 좋은 선택일 때가 있는 것이다. 항목 23 : 연관 컨테이너 대신 정렬된 vector를 쓰는 것이 좋을 때가 있다.
  • 50. - 표준 연관 컨테이너는 이진 탐색 트리로 구현되어 있다. (탐색, 삽입, 삽입, 탐색, 삭제, 삽입, 탐색) 즉, 다음에 어떤 동작이 이루어질지 예측할 수 없는 구조이다. - 보통 애플리케이션은 이렇게 혼란스럽게 자료구조를 사용하지 않는다. 1.데이터 셋업: 자료구조를 만들고 데이터 요소를 넣는다. (삽입/삭제가 대부분이고 탐색은 하지 않는 다.) 2.데이터 탐색: 셋업이 끝난 자료 구조를 사용하여 원하는 정보가 있는지 찾는다. (말 그래도 탐색만 한다.) 3.데이터 재구성: 자료 구조에 들어 있는 내용물을 바꾼다. 1의 동작과 흡사하다. 이후 다시 2단계로 진입한다. - 이런 식의 자료구조를 사용하는 구조라면 벡터가 더 효율적일 가능성이 높다.(수행성능, 메모리 모 두) 하지만 이 벡터는 정렬 되어있어야 한다는 제약은 따른다. 항목 23 : 연관 컨테이너 대신 정렬된 vector를 쓰는 것이 좋을 때가 있다.
  • 51. - 메모리 문제. 연관컨테이너일 경우 2진 탐색 트리를 사용하므로 객체 1개만 담아도 자식 노드2개가 필수적으로 붙 는 오버헤드가 발생한다. 벡터라고 한다면 여유공간을 잡아 놓는 오버헤드가 존재하긴 하지만, 그리 크지 않다. 정렬된 vector에 대한 데이터 저장은 연관 컨테이너에 대한 저장보다 메모리 소비량이 적을 가능성이 높고, 페이지 오류를 고려하면 정렬된 벡터를 사용한 이진 탐색은 연관컨테이너를 사용한 탐색보다 빠 를 가능성이 높다. (물론 이경우 항상 정렬되어 있어야 한다는 단점이 있다. 이로 인해 벡터내부가 밀리고, 밀리던 와중에 이주까지 하게 된다면 많은 비용이 들어감에 틀림없지만 그렇다 하더라도 연관 컨테이너 보다 나을 수 있다.) - 그렇기에 삽입&삭제 동작과 탐색 동작이 거의 섞이지 않는 경우에는 정렬된 벡터가 더 좋다. 항목 23 : 연관 컨테이너 대신 정렬된 vector를 쓰는 것이 좋을 때가 있다.
  • 52. - map의 operator[ ] 연산자 함수는 유별난 특징이 있다. “추가 아니면 갱신”의 기능을 수행하도록 설 계 되어있다. - 해당 맵에 키 k 가 있는지 먼저 점검한다. 그렇지 않다면 k와 v 가 페어로 묶여 맵에 추가된다. k가 맵에 있는 경우에는 k와 연관된 값이 v 로 갱신된다. 항목 24 : map::operator[ ]나 map::insert는 효율문제에 주의하여 선택하자.
  • 53. - 추가 하는 경우에는 객체를 임시로 만드는데 필요한 기본생성자, 소멸자, 대입연산자 함수가 호출 되는 것이다. 이때는 operator[] 보다 원하는 값을 생성자 매개변수에 바로 넣는 것이 효율적일 수 있다. - 위 코드는 기능적으로 아래 와 같다. 아래와 같이 호출 한번이 효율적일 것이다. - 갱신의 경우에는 operator[ ]가 효율 적이다. 항목 24 : map::operator[ ]나 map::insert는 효율문제에 주의하여 선택하자.
  • 54. - 해쉬 컨테이너는 연관 컨테이너이다. 새로운 점은 해쉬 함수를 지정해야 한다는 점이다. - 이장에서는 딩컴웨어와, SGI 버전의 해쉬 함수를 알아보고 있다. 항목 25 : 현재는 표준이 아니지만 해쉬 컨테이너에 대해 충분히 대비해 두자.
  • 56. - iterator나 const_iterator를 증가시키면 해당 반복자는 현재 가리키고 있는 요소의 그 다음 요소를 가리킨다. 문제점은 insert 나 erase는 받아들이는 매개변수 타입으로 iterator를 요구한다는 점이다. 이것은 무조건 그냥 iterator이다. - 그림에서 보게 되면 const_iterator를 iterator로 바꾸거나 const_reverse_iterator를 reverse_iterator 로 바꿀 수 없게 되어 있다. 왜냐하면 이 둘로는 STL 표준 컨테이너의 어떤 멤버 함수를 사용하는데 문제가 생기기 때문이다. (그렇기에 상수 반복자는 요소 삽입 위치나 삭제 위치를 지정하는데 쓸모 가 없다) 항목 26 : const_iterator, reverse_iterator, const_reverse_iterator도 좋지만 역시 쓸만 한 것은 iterator 이다.
  • 57. - 하지만 상수반복자는 알고리즘에서 좋은 역할을 한다. 즉 오직 insert와 erase류의 함수에서 문제가 있을 뿐이다. - 어떤 형태의 insert와 erase 멤버 함수는 무조건 iterator만을 넘겨야 한다. - const_iterator를 iterator로 암묵적 변환하지 못한다. - reverse_iterator를 iterator로 변환할 수 있으나, 변환한 후에 약간의 조정이 필요하다. (base() 함수를 사용하고, 왜 이렇게 변환하는지는 항목28에서 알아본다) - 요점은 이 타입 저 타입의 반복자를 석어쓰면 문제가 된다. 최대한 그냥 iterator를 사용하는 것이 좋겠다. 항목 26 : const_iterator, reverse_iterator, const_reverse_iterator도 좋지만 역시 쓸만 한 것은 iterator 이다.
  • 58. - 항목26에서 삽입과 삭제를 하고싶은데 이미 const_iterator를 사용하고 있다면 어찌해야 할까. 이런 경우에는 직접 손을 써야한다. - 캐스팅일까? p190에서의 예와 같이 const_cast를 사용한다 하더라도 에러가 나타난다. (안돼는 이 유는 서로 다른 클래스 이기 때문이다.) 즉 코드의 수정이 필요하다. 항목 27 : const_iterator를 iterator로 바꾸는 데에는 distance와 advance를 사용하자.
  • 59. - iterator 타입을 데큐의 컨테이너의 begin()으로 초기화 한다. 그후 advance함수와 distance 함수를 사용하여 const_iterator위치로 iterator i 를 옮긴다. - 이때 두 함수는 iterator의 인스턴스로 만들어진 템플릿이다. - advance는 어떤 반복자를 지정된 거리만큼 이동(전진)시킨다. - distance는 같은 컨테이너를 가리키고 있는 두 반복자 사이의 거리를 알려준다. - 이제 i 와 ci 가 같은 컨테이너를 가리키고 있는 한 advance(i, distance(i,ci))표현식이 실행 되었을 때 i와 ci는 컨테이너의 같은 위치를 가리킨다. - 하지만 이때 distance의 매개변수 부분이 서로 다른 타입이기때문에 아직 컴파일이 되지 않는다. 이 때에는 타입에 관련된 모호함을 없애 주어야한다. - typedef deque<int> intDeque; - typedef intDeque::iterator Iter; - typedef intDeque::const_iterator ConstIter; - advance(i, distance<ConstIter>(i, ci)); //이와 같이 모호함을 없애 준다. 항목 27 : const_iterator를 iterator로 바꾸는 데에는 distance와 advance를 사용하자.
  • 60. - base 멤버함수를 호출하면 reverse_iterator로 iterator를 얻을 수 있다고 했다. 하지만 정확히 그런 것은 아니다. - 실제로 ri 와 i 가 가리키는 부분이 다르다! 항목 28 : reverse_iterator에 대응되는 기점 반복자(base iterator)를 사용하는 방법을 정확하게 이해하자.
  • 61. - 이러한 이유가 있다. ri를 i 로 바꾸는 이유는 ri로 insert와 erase를 할 수 없기 때문에 바꿔주는 것이 고 ri 일 때는 오른쪽에서 왼쪽으로 컨테이너 요소를 이동하지만 삽입은 반복자가 가리키는 위치의 바로 앞에서 이루어지기 때문이다. - 즉 요소 삽입이 목적이라면 ri.base()는 ri에 정확히 대응되는 반복자이다. - 그렇다면 삭제는 어떨까? 삭제는 기점 반복자(base()) i 를 사용할 수 없다. i가 가리키는 앞에 있는 요소를 삭제해야 맞는 것이다. - 즉 reverse_iterator인 ri 로 지정된 위치에 있는 요소를 삭제하려면 ri.base()의 앞에 있는 위치에서 삭제를 수행해야한다. 요소 삭제가 목적이라면 ri 와 ri.base()는 동등하지 않으며 ri에 대응되는 iterator가 아니다. - 그렇기에 다음과 같은 아이디어를 사용한다. v.erase((++ri).base()); 항목 28 : reverse_iterator에 대응되는 기점 반복자(base iterator)를 사용하는 방법을 정확하게 이해하자.
  • 62. - 어떤 텍스트 파일을 읽을때, istream_iterator가 사용하는 operator>> 함수는 서식화 입력을 수행한 다. (여러 뒷처리를 알아서 해준다) - 그렇기에 서식화 입력을 할 대는 매우 소중하고 값진 것이지만, 그냥 입력 스트림으로부터 다음 문 자를 뽑아내는 일만 하고싶다면 비용손실이 많다. - 이때 istreambur_iterator를 사용해보자. 이 반복자는 스트림 자체의 버퍼를 직접 건드려서 다음 문 자들을 바로 읽어 들인다. 심지어 이 것은 공백문자도 무시하지 않는다. 또한 속도도 상대적으로 빠 르다. 항목 29 : 문자 단위의 입력에는 istreambuf_iterator의 사용도 적절하다.