* STL을 구성하는 핵심 요소에는 여러 가지가 있다.(Iterator, Generic Algorithm, Container 등등). 역시 가장 핵심적이고, 조금만 알아도 쓸수 있고, 편하게 쓸수 있는 것은 Container다. Container는 Vector, List, Deque 과 같이 데이터를 담는 그릇과 같은 Object라고 보면 된다. * STL의 Container들의 장점이라고 한다면, 역시 유연성, 메모리 관리 알아서 하기, 자신이 알아서 늘었다 즐었다 하기 등등이 있겠다. 또한 인터페이스가 이거나 저거나 비슷비슷해서 하나만 공부하면, 쉽게 다른것도 쓸수 있다는 것도 또 하나의 장점이 될수 있겠다. * 큰 1장 Containers에서는 상황에 맞는 적절한 Container 고르는 법, 효율성 극대화 하기 등등을 다룬다. [[TableOfContents]] = Item1. Choose your containers with care. = == STL이 지원하는 Containers == * Sequence Containers - vector, deque, list, string( vector ) 등등 * Associative Containers - set, multiset, map, multimap 등등 * 그 외에 Non Standard라고 써있는게 있긴 한데, 잘 안쓰는 것 같다. 혹시라도 나중에 중요성이 부각되면 다시 봐야겠다. 딴게 급하니 일단.. AfterCheck * vector가 좋다고 써있다. 잘 쓰자. 가끔가다 시간,저장공간 등등에 있어서 Associative Containers를 압도할때도 있다고 한다. * vector 는 Sequence Container 니까 보통 Associative Container 들(주로 Map)보다 메모리낭비가 덜함. 그대신 하나 단위 검색을 할때에는 Associative Container 들이 더 빠른 경우가 있음. (예를 들어 전화번호들을 저장한 container 에서 024878113 을 찾는다면.? map 의 경우는 바로 해쉬함수를 이용, 한큐에 찾지만, Sequence Container 들의 경우 처음부터 순차적으로 좌악 검색하겠지.) --[1002] == vector, deque, list 고르는 팁 == * vector는 무난하게 쓰고 싶을때 * list는 중간에서 삽입, 삭제가 자주 일어날때 * deque는 종단점(시작과 끝)에서 삽입, 삭제가 자주 일어날때 == Contiguous-memory Containers & Node-based Containers == * 전자는 배열을 기반으로 하는 Container(즉, 동적 메모리 할당을 할때 할당된 메모리들이 연속적이 된다), 후자는 노드를 기반으로 하는 Container(노드의 경우는 반드시 연속적인 메모리가 아닐 수 있다.)다. 노드가 뭔지는 링크드 리스트를 짜보면 알게 된다. * 전자에는 vector, string, deque 등이 있다. 뭔가 또 복잡한 말이 있긴 한데 그냥 넘어가자. 대충 insert, delete가 일어나면 기존의 원소가 이동한다는 말 같다. * 후자에는 list 등이 있다. 노드는 그냥 포인터만 바꿔 주면 insert, delete가 자유자재로 된다는거 다 알것이다. == STL Container 고르는 팁 == * 양끝에서 insert, delete 하려면? Associative Containers는 쓰면 안된다. * Random Access Iterator(임의 접근 반복자)가 필요하다면, vector, deque, string 써야 한다. (rope란것도 있네), Bidirectional Iterator(양방향 반복자)가 필요하다면, slist는 쓰면 안된다.(당연하겠지) Hashed Containers 또 쓰지 말랜다. * Insert, Delete 할때 원소의 인덱스 위치가 변하면 안된다? 그러면 Contiguous-memory Containers는 쓰면 안된다. (해당 원소의 인덱스 위치가 변한다.) * Search 속도가 중요하다면 Hashed Containers, Sorted Vector, Associative Containers를 쓴다. (바로 인덱스로 접근, 상수 검색속도) * Insert, Delete를 효율적으로 쓰려면, Node Based Containers를 쓰자. * Iterator, Pointer, Reference 갱신을 최소화 하려면 Node Based Containers를 쓴다. ---- = Item2. Beware the illusion of container-independant code. = == 완벽한 Container 독립적인 코드 작성은 불가능? == * Standard Contiguois-memory Container들은 임의접근 연산자 []을 지원하지만, 다른건 지원하지 않는다. * Standard Node-based Container들은 양방향 반복자(bidirectional Iterator)를 지원한다. * 결국 이 코드에서 이 컨테이너에서 작동하는 함수를 썼는데, 컨테이너를 같은 계열의 것으로 바꾸면 좋겠지만, 성향이 다른걸로 바꾸면--; 많이 고쳐야 할것이다. * 각각의 컨테이너는 장점과 단점을 가지고 있다. 고르는 능력을 기르자. ''STL Tutorial and Refereince 를 보면, 일부러 해당 Container 에게 최적화된 메소드들만 지원한다고 써있었던 기억. 예를 든다면, Vector 의 경우 push_front 같은 것이 없다. (만일 vector에 push_front 를 한다면? push_front 될때마다 매번 기존의 원소들 위치가 바뀐다.) --[1002]'' == 그나마 고치기 쉽게 하는 팁들 == * typedef을 쓰자. {{{~cpp vector::itarator // typedef vector::iterator VTIT 이런식으로 바꿀수 있다. 앞으로는 저렇게 길게 쓰지 않고도 VIIT 이렇게 쓸수 있다. }}} * Encapsulization을 잘 하자. 바뀌는 것에 대한 충격이 줄어들 것이다. ---- = Item3. Make copying cheap and correct for objects in containers. = == STL way == * 컨테이너에 넣을 때나 뺄 때, 복사를 한다. * 컨테이너에 Object를 넣을때에는, 그 Object의 복사 생성자(const reference)와, 대입 연산자를 재정의(const reference) 해주자. 안그러면 복사로 인한 엄청난 낭비를 보게 될것이다. == 상속받은 객체를 넣을때의 문제점 == * Slicing에러 {{{~cpp vector vw; class SpecialWidget : public Widget ... SpecialWidget sw; vw.push_back(sw) // 어떻게 될까. 복사되면서 SpecialWidget만의 특수성은 제거되고, Widget의 특성만 남게 된다. }}} * 해결책으론 가상함수 등등이 있다. 하지만 더 좋은 방법이 있다. == 해결책 == * 컨테이너에 값을 넣지 말고, 포인터를 넣는 것이다. {{{~cpp vector vw; vw.push_back(new SpecialWidget); // 잘된다. }}} * 하지만 역시 내가 delete해줘야 한다는 단점이 있다. 대안으로 smart pointers라는게 있다.(이게 뭘까) ["MoreEffectiveC++/Techniques1of3"] 의 Item 28 을 봐라. 영문을 보는것이 더 좋을것이다. ["MoreEffectiveC++"] 의 전자책과 아날로그 모두다 소장하고 있으니 필요하면 말하고, 나도 이 책 정리 할려고 했는데, 참여하도록 노력하마 --["상민"] == Why copy? == * 불필요한 객체의 복사를 막기 위해 디자인 되어있다.(잘 이해가 안간다.) * 내가 요구할때만 그만큼 많은 공간을 할당한다. ---- = Item4. Call empty instead of checking size() against zero. = * 컨테이너에 하나도 안들었다는것을 가정할때 size() == 0 이것보다 empty() 가 더 좋다고 한다. * empty()는 상수 시간에 수행이 가능하고, size()는 선형 시간에 수행이 가능하다. * 고로 empty() 쓰자. ---- = Item5. Prefer range member functions to their single-element counterparts. = * 범위 가지고 노는 메소드가 하나씩 가지고 노는 메소드보다 우월하다. 수행시간의 차이가 있는듯하다. == a 컨테이너에 있는 내용의 뒷부분의 절반을 b라는 벡터 컨테이너에 넣고 싶다면, 어떻게 해야할까? == * STL에 익숙하지 않다면 아마도 다음과 같이 할 것이다. {{{~cpp // 명시적인 반복문을 쓴다. vector a,b; b.clear(); for(vector::const_itarator VWCI = a.begin() + a.size()/2 ; VWCI != a.end() ; ++VWCI) b.push_back(*VWCI) }}} * 약간 익숙한 사람이라면.. {{{~cpp // copy 알고리즘을 이용한다. vector a,b; b.clear(); copy( a.begin() + a.size() / 2, a.end(), back_inserter(b)) ; }}} * 어느 정도 알고, 익숙한 사람이면 다음과 같이.. {{{~cpp // assign 메소드를 사용한다. vector a,b; b.assign(a.begin() + a.size() / 2, a.end()); }}} * 이 네 가지 방법을 보자. 첫번째 두번째 방법은 루프를 사용한다. 두번째 방법에 루프가 어딨냐고 물으면 나는 모른다. copy 알고리즘 내부에서 루프를 사용한단다. 하지만 assign 메소드는 루프를 사용하지 않고 한번에 짠! 해주는거 같다. * copy, push_back 이런것은 넣어줄때 insert iterator를 사용한다. 즉, 하나 넣고 메모리 할당 해주고, 객체 복사하고(큰 객체면... --; 묵념), 또 하나 넣어주고 저 짓하고.. 이런것이다. 하지만 assign은 똑같은 작업을 한번에 짠~, 만약 100개의 객체를 넣는다면 assign은 copy이런것보다 1/100 밖에 시간이 안걸린다는 것이다.(정확하진 않겠지만.. 뭐 그러하다.) * 또 하나의 문제점, insert 메소드는 실행할때마다 새로운 공간을 할당하기 위해 하나씩 밀린다. 만약 컨테이너가 n개의 객체를 가지고 있고, 거기다 m개의 객체를 insert로 넣으면.. n*m만큼 뒤로 땡기느라 시간을 낭비하게 된다. int같은 기본 자료형이면 괜찮을지도 모르지만.. 만약에 객체가 큰 경우라면, 대입 연산자, 복사 생성자 이런것도 저만큼 호출하게 된다. 미친다. * range 멤버 메소드는 주어진 두개의 반복자로 크기를 계산해서 한번에 옮기고 넣는다. 벡터를 예로 들면, 만약에 주어진 공간이 꽉 찼을때, insert를 수행하면, 새로운 공간 할당해서 지금의 내용들과, 넣으려는 것들을 그리로 옮기고 지금 있는걸 지우는 작업이 수행된다. 이짓을 100번 해보라, 컴퓨터가 상당히 기분나빠할지도 모른다. 하지만 range 멤버 메소드는 미리 늘려야 할 크기를 알기 때문에 한번만 하면 된다. * 성능 따지기 골치 아픈 사람이라도, range 시리즈가 하나씩 시리즈보다 타이핑 양이 적다는 걸 알것이다. ---- = Item6. Be alery for c++'s most vexing parse = == 문제점? == * 아직까지도 STL을 완벽하게 지원하는 컴파일러는 존재하지 않는다. * 잊어버리고 있었던 클래스의 생성자에 관한.. {{{~cpp class Widget {...}; // 디폴트 생성자가 있다고 가정 Widget a // 맞다. Widget b() // 과연? }}} * Widget b() 이것은 Widget형의 객체를 리턴해주는 b라는 이름을 가진 함수다. 이것과 관련해 파싱 에러가 자주 일어난다고 한다. == 예제 : ints.dat 에 저장되어 있는 정수들을 읽어서 list data에 쓰는 작업을 한다. == {{{~cpp ifstream dataFile("ints.dat"); list data(ifstream_iterator(dataFile),ifstream_iterator()); // 이런 방법도 있군. 난 맨날 돌려가면서 넣었는데..--; }}} * 왠지는 모르겠지만 두번째 문장은 작동하지 않는다. 위에서 제기한 문제 때문(괄호에 관련된)이다. 별루 중요한 것 같진 않으니 그냥 넘어감. 그래도 해결책은.. == 해결책 == {{{~cpp ifstream dataFile("ints.dat"); ifstream_iterator dataBegin(dataFile); ifstream_iterator dataEnd; list data(dataBegin, dataEnd); // 요런식으로 써주자. }}} == 잡담 == * C++이 이렇게 애매한줄은 몰랐다. * STL에서 반복자로 돌리는건 표준 스타일이란다. 그렇게 하도록 하자. ---- = Item7. When using containers of newed pointers, remember to delete the pointers before the container is destroyed = == 서론 == * 누누히 강조했던 new로 만든거 넣었으면, delete 해주자는 것이다. * 컨테이너가 파괴될때 포인터는 지워주겠지만, 포인터가 가리키는 객체는 destroy되지 않는다.(Detected Memory Leaks!) == 예제 == {{{~cpp vector v; for(int i = 0 ; i < 10 ; ++i) v.push_back(new Object); // new로 넣어줬다. ... ... for(vector::iterator i = v.begin() ; i != v.end() ; ++i) delete *i // 지워주자. return 0; }}} * 하지만 ... 부분에서 예외가 터져서 프로그램이 끝나버린다면? 또다시 Detected Memory Leaks! == 보완법 1(class for function object) == * 이를 보완하기 위해 delete를 함수 객체로 만드는 법이 있다.(뭐야 이거?) * Fucntion Object 보통 class키워드를 쓰던데, struct(이하 class라고 씀)를 써놨군. 뭐, 별 상관없지만, 내부 인자 없이 함수만으로 구성된 class이다. STL에서 Generic 을 구현하기에 주효한 방법이고, 함수로만 구성되어 있어서, 컴파일시에 전부 inline시킬수 있기 때문에 최적화 문제를 해결했음. 오 부지런히 보는가 보네. 나의 경쟁심을 자극 시키는 ^^;; --["상민"] {{{~cpp struct DeleteObject : public unary_function { template void operator()(const T* ptr) const { delete PTR; } }; void f() { ... for_each(v.begin(), v.End(), DeleteObject()); // 정말 이상하군..--; } }}} * 밑에꺼 쓰고 덧붙이는 건데 이 방법은 열라 안좋은 거란다.(struct DeleteObject, 쳇 그러면서 뭐하러 설명해?-.-) == 보완법 2(Smart Pointer) == * 또 다른 방법으로 컨테이너에 포인터를 넣는 대신 스마트 포인터를 넣는 것이다.(아악..--; MDC++(MEC++)을 봐야 하는가..ㅠ.ㅠ) {{{~cpp typedef boost::shared_ptr SPO; // boost 라이브러리라는게 있단다. vector v; for(int i = 0 ; i < 10 ; ++i) v.push_back(SPO(new Object)); }}} ---- = Item8. Never create containers of auto_ptrs. = * 생략. 도무지 뭔말하는건지 모르겠다. COAP는 또 뭐고..--; * 컨테이너를 오토 포인터로 생성하지 말것~ ---- = Item9. Choose carefully among erasing options. = == 어떤 컨테이너가 int값들을 담고 있다고 하자. 거기서 1982 라는 숫자를 몽땅 지워주고 싶다면? == * Contiguous-memory container 일때 {{{~cpp c.erase( remove(c.begin(), c.end(), 1982), c.end() ); // 이건 내부적으로 어떻게 돌아가는 걸까. 찾아봐야겠군. }}} * list일때 - erase 써도 되지만 remove가 더 효율적이다. {{{~cpp c.remove(1982); }}} * Associative container 일때 - remove쓰면 난리난다.(없으니깐--;) 또 제네릭 알고리즘 remove도 역시 안된다. 컨테이너가 망가질수도 있다. {{{~cpp c.erase(1982); }}} == 조건 검사해서 몽땅 지워주기 == * 또 이상한 팁이 있군. bool형을 리턴하는 함수를 인자로 같이 넣어주면 그 조건을 만족하는 값은 모조리 지워주는..(함수 포인터일까.. 함수 포인터는 아직도 언제 쓰는건지 모르겠다. 혹시 아시는분은 좀 가르쳐 주세요^^;) * 함수포인터임. (또는 위에서의 함수객체.) 해당 함수 객체로 조건을 파악한뒤 삭제하거나 비교,소트 하는 일을 많이 함. --[1002] === 쉽게 할수 있는 컨테이너들 === {{{~cpp bool badValue(int x) { ... } // 넣어줄 함수 선언 }}} {{{~cpp c.erase( remove_if(c.begin(), c.end(), badValue), c.end() ); // Contiguous-memory Container일때(vector, deque, string) }}} {{{~cpp c.remove_if(badValue); // list일때 }}} === Associative Container 일때 === * 쉽지만 비효율적인 방법 - remove_copy_if 를 사용한다. 이것은 지우고 싶은걸 제외한 나머지 것들을 새로운 컨테이너로 옮긴다. 그 다음에 원래 컨테이너를 새로 만든것과 바꿔 버린다.(우울하군--;) {{{~cpp AssocContainer c; // map,multimap,set,multiset 등을 일반화한 이름이다. 실제로 저런 이름의 컨테이너는 없다.--; ... AssocContainer goodValues; remove_copy_if(c.begin(), c.end(), inserter(goodValues, goodValues.end()), badValue); // 헉 이분법--;. 보면서 느끼는 거지만 정말 신기한거 많다. 저런 것들을 도대체 무슨 생각으로 구현했을지.. c.swap(goodValues); // c랑 goodValues랑 바꾼다. 이런것도 있군. }}} * 어렵지만 효율적인 방법 - 뭐 또 파일에 쓴다고 해서 이상한방법 쓰는데 그냥 넘겼다. {{{~cpp AssocContainer c; for(AssocContainer::iterator i = c.begin() ; c != c.end() ; ) { if(badValue(*i)) { c.erase(i++); // 지워야 할 값이면 일단 지우고 반복자 하나 증가시켜준다. 후위 연산자는 그 값을 미리 복사를 하기 떄문에 가능한 일이다. } // 그냥 지우면 그 반복자는 void가 된다. 안좋다--; else ++i; // 아니면 그냥 증가 } }}} == 잡담 == * 반복자를 이용해서 루프를 돌다가 어떤 걸 지우면, 그걸 가리키고 있던 반복자는 갱신된다. 다음에 어떻게 될지 장담 못한다는 뜻이다. 주의하자. ---- = Item10. Be aware of allocator conventions and restrictions. = ---- = Item11. Understand the legitimte uses of custom allocators. = ---- = Item12. Have realistic expectations about the thread safety of STL containers. = ---- [EffectiveSTL]