1 / 51

Effective STL

Effective STL. hermet@naver.com. 효과적인 컨테이너 요리법 vector 와 string STL 연관 컨테이너 반복자 ( iterators ) 알고리즘 (algorithms) 함수자 , 함수 객체 , 함수 , 기타 등등 STL 프로그래밍을 더 재미있게 해주는 팁 모음 * 본 자료는 오직 개인적인 학습을 목표로 두었으며 어떠한 상업적 목표도 없음을 밝힙니다 .

heba
Download Presentation

Effective STL

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Effective STL hermet@naver.com

  2. 효과적인 컨테이너 요리법 vector 와 string STL 연관 컨테이너 반복자(iterators) 알고리즘(algorithms) 함수자, 함수 객체, 함수, 기타 등등 STL 프로그래밍을 더 재미있게 해주는 팁 모음 * 본 자료는 오직 개인적인 학습을 목표로 두었으며 어떠한 상업적 목표도 없음을 밝힙니다. * 본 자료는 Scott Meyer 저자의 Effective STL (정보 문화사) 의 서적을 토대로 제작되었음을 밝힙니다. Contents

  3. 1. 효과적인 컨테이너 요리법 • 시작해 볼까요?

  4. a. 거의 모든 컨테이너들은 같은 이름의 헤더에 정의 • vector- <vector> • list- <list> • set, multiset - <set> • map, multimap - <map> b. 네 개를 제외한 모든 알고리즘이 <algorithm>에 정의 • 제외된 네 개 accumulate, inner_product, adjacent_difference, partial_sum • 위의 네 개는 <numeric>에 선언되어 있음. c. istream_iterator와 istreambuf_iterator를 포함한 특별한 종류의 반복자는 <iterator>에 정의 d. 표준 함수자와 함수자 어댑터는 <functional>에 선언 • 표준 함수자 예 – less<T> • 함수자 어댑터 예 – not1, bind2nd 1.용도에 맞는 헤더를 항상 #include 하자(항목48)

  5. 표준 시퀀스 컨테이너 : vector, string, deque, list a. Vector는 기본적으로 사용되는 시퀀스 b. List는 시퀀스 중간에 빈번한 삽입, 삭제가 수행될 필요가 있을 시 사용 c. Deque는 대부분의 삽입과 삭제가 시퀀스 앞과 끝에에서 일어날 때 사용 • 표준 연관 컨테이너 : set, multiset, map, multimap • 비표준 시퀀스 컨테이너 : slist, rope a. slist : 단일 연결 리스트 b. rope: 대용량 string • string 대신 사용되는 vector<char> • 표준 연관 컨테이너 대신 사용되는 vector • STL에 속하지 않는 표준 컨테이너 a. bitset, valarray, stack, queue, priority_queue 2.적재적소에 알맞은 컨테이너를 사용하자 - 1(항목1)

  6. 연속 메모리 컨테이너 와노드 기반 컨테이너 • 연속 메모리 컨테이너 : 새 요소가 삽입되거나 이미 있던 요소가 지워지면 같은 메모리 단위에 있던 다른 요소들은 앞혹은 뒤로 밀려나면서 새 요소가 삽입될 공간을 만들든지 지워진 공간을 메움. ‘밀어내기’ 때문에 수행 성능의 발목을 잡을 수 있다. - vector, string, deque, rope b. 노드 기반 컨테이너 : 삽입 혹은 삭제되었을 때 노드의 포인터만 영향을 받지, 나머지 요소들이 밀려나는 일이 없다. – list, slist 2.적재적소에 알맞은 컨테이너를 사용하자 - 2(항목1)

  7. 컨테이너의 아무 위치에 요소를 삽입할 수 있어야 하나? -시퀀스 컨테이너를 사용하세요~ • 요소들의 순서 결정에 직접 관여할 필요가 없다면? -해쉬 컨테이너를 사용하세요~ • 임의 접근 반복자가 필요하다면? -vector, deque, string, rope등을 고려해 보세요~ • 요소 삽입이나 삭제시 다른 요소들이 밀려나는 일이 없어야 한다면? -당연하겠지만 연속 메모리 컨테이너에 손 대지 말 것! • 데이터가 c의 데이터 타입과 메모리 배열 구조적으로 호환되어야 한다면? -vector밖에 없습니다. • 탐색 속도가 가장 중요하다면? -해쉬 컨테이너, 정렬된 vector, 표준 연관 컨테이너 중 하나. • 컨테이너 참조 카운팅이 신경 쓰인다면? -string을 피하라~ • 삽입과 삭제를 안정적으로 되돌릴 수 있어야 한다면? -노드 기반 컨테이너를 고려해 보세요~ • 반복자, 포인터, 참조자가 무효화되는 일을 최소화 해야 한다면? -노드 기반 컨테이너를 사용하세요~ • 시퀀스 컨테이너가 필요한데, 요소 삭제가 일어나지 않고 요소 삽입이 컨테이너 끝에서만 일어난다면? 포인터와 참조자가 무효화되지 않아야 한다면? -정답은 deque입니다. 2.적재적소에 알맞은 컨테이너를 사용하자 - 3(항목1)

  8. 컨테이너 안에 객체에는 복사되어 들어가고 복사되어 나온다는 사실. 따라서 복사 생성자와 복사 대입 연산자가 사용되므로 항상 염두해 두세요. class Widget { public: Widget( const Widget& ) { … } Widget& operator=(const Widget& ) { … } }; • 포인터 컨테이너를 사용할 경우 포인터를 복사하되 스마트 포인터가 괜찮은 방법 중 하나입니다. 3.복사는 컨테이너 안의 객체에 맞게 비용은 최소화하며, 동작은 정확하게 하자 (항목3)

  9. class SP{ public:     M* m_M;    M* operator->(){ return m_M;}}; • void main(){    SP A;    A->m_I=10; // (A.m_M)->m_I=10;과 같은 코드;         A->Fm(); // (A.m_M)->Fm();과 같은 코드;} • 출력결과는 물론 10이겠죠...주석처리한 코딩으로 바꾸어도 같은 결과를 얻습니다.그리고,A->m_I라는 표현은 (A.operator->())->m_M과도 같은 표현 입니다. 잠시! 스마트 포인터(Smart Pointer)란?

  10. If( c.size() == 0 ) -> if( c.empty() == true ) a. empty는 인라인 함수로 구현되어 있음. b. empty는모든 표준 컨테이너에 대해 상수 시간에 수행되지만, 몇몇 제품에서 구현된 list 클래 스에선size가 선형 시간에 수행되는 경우가 많다. c. 따라서,empty가 더 빠르다. 4.size() 결과를 0과 비교할 생각이라면 차라리 empty()를 호출하자 (항목4)

  11. 벡터 v1과 v2가 있을 때, v1의 내용을 v2의 뒤쪽 반과 똑같이 만드는 가장 빠른 방법은? a. int index = 0; for( itr = v2.begin() + v2.size() / 2 ; itr < v2.end(); ++itr ) { v1[ index ] = itr->second; ++index; } 5.단일 요소를 단위로 동작하는 멤버 함수보다 요소의 범위를 단위로 동작하는 멤버 함수가 더 낫다 -1(항목5) b. v1.assign( v2.begin() + v2.size() / 2, v2.end() ); 컨테이너 내용을 다른 범위의 것과 대체하고 싶다면 대입(assign) 하라! c. 장점? - 코드가 대게 짧다. - 훨씬 명확하고 간결한 의미가 전달된다.

  12. 알고리즘을 사용한 범위 단위의 멤버 함수의 예. 1. int data[ numValues ]; vector< int > v; v.insert( v.begin(), data, data + numValues ); //insert 사용시 요소를 마지막 위치까지 바로 옮기도록 되어 있음. 따라서 for문에 비해 n*( numValues + 1) 번의 이동 횟수가 줄어든 것과 같음. 2. vector<Widget> v1, v2; ………. v1.clear(); copy( v2.begin() + v2.size() / 2, v2.end(), back_inserter( v1 ) ); //컨테이너의 메모리가 꽉 찼을 경우 for문으로 요소를 삽입할 시, 하나 삽입할 때마다 저장할 메모리 를 할당 하는 반면, 범위 버전의 함수를 사용할 경우 처음, 한번에 필요한 메모리를 할당해 버리기 때 문에 성능상 우월 5.단일 요소를 단위로 동작하는 멤버 함수보다 요소의 범위를 단위로 동작하는 멤버 함수가 더 낫다 -2(항목5)

  13. 참고사항 • 범위 생성 : 모든 표준 컨테이너는 다음과 같은형태의 생성자를 지원 container :: container( InputIterator begin, InputIterator end ); • 범위 삽입 : 모든 표준 컨테이너는 다음과 같은 형태의 insert를 지원 void container :: insert( iterator position, InputIterator begin, InputIterator end ); void container :: insert( InputIterator begin, InputIterator end ); • 범위 삭제 : 역시 표준 컨테이너는 범위 버전의 erase를 제공 iterator container :: erase( iterator begin, iterator end ); <- 시퀀스 컨테이너 void container :: erase( iterator begin, iterator end ); <- 연관 컨테이너 • 범위 대입 : 모든 표준 시퀀스 컨테이너는 범위 버전의 assign을 제공 void container :: assign( InputIterator begin, InputIterator end ); 5.단일 요소를 단위로 동작하는 멤버 함수보다 요소의 범위를 단위로 동작하는 멤버 함수가 더 낫다 -3(항목5)

  14. vector< int > v; v.reserve( 10 ); for( int I = 0; I < 10; ++I ) v.push_back( I ); v[3] = v[5] = v[9] = 99; remove( v.begin() , v.end(), 99 ); cout << v.size(); -> ? 6.요소를 정말 제거 하고 싶다면 remove 류의 알고리즘 뒤 에 꼭 erase를 붙여서 사용하자! (항목32) 답은 10. “remove는 어느 것도 진짜로 없애지 않는다! 없앨 수 없기 때문이다.” 해결책 v.erase( remove( v.begin(), v.end(), 99 ), v.end() ); cout << v.size(); -> 7 1 2 3 99 5 99 7 8 9 99 1 2 3 5 7 8 9 ? ? ?

  15. vector< Widget*> v; Widget* w = new Widget(); v.push_back( w ); …. delete v[0]; v.erase( v.begin() + 0 ); …더 멋지게 삭제할 수 있을까? 7.New로 생성한 포인터 컨테이너를 사용할 때는 컨테이너가 소멸되기 전에 포인터를 delete 하는 일은 잊지 말자! (항목7) template< typename T > structDeleteObject : public unary_function< const T*, void > { void operator()( const T* ptr ) const { delete ptr; } }; void doSomething() { for_each( vwp.begin(), vwp.end(), DeleteObject<Widget>() ); } …더 멋지게 구현할 순 없을까? structDeleteObject { • template< typename T > void operator()( const T* ptr ) const { delete ptr; } }; void doSomething() { deque< SpecialString* > dssp; for_each( dssp.begin(), dssp.end(), DeleteObject() ); }

  16. 1. 연속 메모리 컨테이너( vector, deque, string ) : erase-remove 합성문. -> c.erase( remove( c.begin(), c.end(), 1963 ), c.end() ); 2. 양방향 반복자를 지원하는 list 및 list 는 멤버함수인remove -> c.remove( 1963 ); 3. 연관 컨테이너에서 특정한 값을 가진 요소를 지우는 것은 erase -> c.erase( 1963 ); 4. 술어구문, true를 반환화는 요소를 지우고 싶다면? remove_if -> boolbadValue( int x ); -> c.erase( remove_if( c.begin(), c.end(), badValue ), c.end() ); //vector, string, deque -> c.remove_if( badValue ); // list 참고) vector, string, deque에 속해 있는 연속 메모리 컨테이너는 erase를 호출하면 지워진 요소를 가리키는 반복자만 무효화되는 것이 아니라 그 외의 모든 반복자가 무효화가 된다! 8.데이터를 삭제할 때에도 조심스럽게 선택할 것이 많다. (항목9)

  17. 여러 쓰레드에서 읽는 것은 안전하다. – 하나 이상의 쓰레드가 하나의 컨테이너의 내용을 동시에 읽어 내는 경우가 있는데, 제대로 동작합니다. 그러나 읽기 도중에 쓰기 동작이 수행되면 안되겠죠. • 여러 쓰레드에서 다른 컨테이너에 쓰는 것은 안전하다. – 하나 이상의 쓰레드가 다른 컨테이너에 동시에 쓸 수 있다. • 그럼 쓰레드의 안정성을 보장하려면? a. 컨테이너의 멤버 함수를 호출하는 시간 동안에 컨테이너에 락을 걸기 b. 컨테이너가 만들어 내어 주는 반복자의 유효 기간 동안에 컨테이너에 락을 걸기 c. 컨테이너에 대해 실행된 알고리즘의 수행 시간 동안에 컨테이너에 락을 걸기 vector< int > v; …. vector< int > :: iterator first5( find( v.begin(), v.end(), 5 ) ); If( first5 != v.end() ) { v.erase( first5 ); } 한 쓰레드에서 요소 를 찾는 중 다른 쓰레드에서 요소를 삽입한다면? 반복자는 무효화가 되어 문제 발생! 9.STL컨테이너의 쓰레드 안정성에 대한 기대는 현실에 맞추 어 가지자. -1 (항목12)

  18. 방법 A vector< int > v; …. getMutexFor( v ); vector< int > :: iterator first5( find( v.begin(), v.end(), 5 ) ); If( first5 != v.end() ) { v.erase( first5 ); } releaseMutexFor( v ); 방법 B template< typename Container > class Lock { public: Lock( const Container& container ) : c( container ) { getMutexFor( c ) ); ~Lock() { releaseMutexFor( c ) }; private: const Container& c; }; vector< int > v; { Lock< vector<int> > lock( v ); vector< int > :: iterator first5( find( v.begin(), v.end(), 5 ) ); If( first5 != v.end() ) { v.erase( first5 ); } } //블록이 끝나면 Lock 소멸자 호출 9.STL컨테이너의 쓰레드 안정성에 대한 기대는 현실에 맞추 어 가지자. -2 (항목12)

  19. 2. vector 와 string • 할만 한데?

  20. 가장 큰 이유? string은 참조 카운팅이 동작되도록 구현되어 있는데, 불필요한 메모리 할당과 문자 복사를 없앰으로써 높은 수행성능 보장 만약 메모리가 문제 되지 않고 참조 카운팅이 부담스럽다면, 참조 카운팅이 없는 vector< char >를 고려해 보아라. 1.동적 할당된 배열 보다는 vector와 string이 낫다. (항목13)

  21. 1. 컨테이너에 담을 수 있는 최대 용량은 max_size를 호출해 보면 알 수 있다. 2. 재할당의 경우 다음과 같은 네 단계로 진행된다. a. 컨테이너의 현재 용량의 몇 배가 되는 메모리 블록을 새로 할당한다. 보통 2배 만큼 늘림. b. 컨테이너가 원래 가지고 있었던 메모리에 저장된 모든 요소 데이터를 새 메모리에 복사한다. c. 원래의 메모리에 저장된 모든 객체를 소멸 시킨다. d. 원래의 메모리를 해제 합니다. 춘언 왈 ) 헉! 이거 완젼쇼킹이구만0_0;; 3. 따라서! 사용할 컨테이너 메모리를 미리 reserve로 할당해 둠으로써 자동 재할당의 횟수를 최소화 시켜 주자! 비용 부담을 상당히 줄일 수 있다. size() - 현재 컨테이너에 들어 있는 요소 개수 알려줌 capacity() – 현재 컨테이너의 할당된 메모리로 담을 수 있는 요소의 개수를 알려 줌 resize( size_t n ) - 컨테이너가 담을 수 있는 요소의 개수를 n으로 만듬 reserver( size_t n ) – 컨테이너의용량을 최소 n개로 맞춤 2.reserve는 필요 없이 메모리가 재할당되는 것을 막아준다. (항목14)

  22. vector<bool> v; bool* pb = &v[0]; 3.vector<bool> 보기를 돌같이 하자(항목18) 컴파일이 안되네요 -0-; 그 이유는 공간을 줄이기 위해 bool을 압축시킨 데이터 표현 방식을 쓰기 때문에 컴파일에서 실패합니 다. 대게 “vector”에 저장되는 “bool”을 하나의 비트로 나타내어 한 바이트에 여덟 개의 bool을 담을 수 있게 구현합니다. 그럼 어떡하죠? 1. deque<bool> 2. bitset

  23. 3. STL 연관 컨테이너 • …………………….

  24. find 알고리즘 같은 경우 상등성(equality)이 기준인 반면, set :: insert는 동등성( equivalence )가 기준. 무슨 차이지? 상등성: operator == 에 뿌리를 두고 있음. class Widget { private: TimeStamplastAccessed; }; bool operator==( const Widget& lhs, const Widget& rhs ) { //lastAccessed는 고려하지 않는다고 정의 } 그럼 두 개의 Widget은 lastAccessed필드 값이 다르더라도 두 객체는 같다는 판정. 동등성 : 정렬순서를 생각하면 됩니다. x < c, y < c x 와 y는 동등하다고 판정. 1.상등 관계와 동등 관계의 차이를 파악하자.(항목19)

  25. set< int, less_equal<int> > s; //s는 <= 에 의해 정렬됩니다. s.insert( 10 ); s.insert( 10 ); 먼저 들어간, 10과 나중에 들어간 10을 비교하게 된다. less_equal를 이용해 비교함수 <= 가 쓰인다. !( 10A <= 10B) && !( 10B <= 10A ); !( true ) && !( true ) false && false False 따라서 둘은 동등하지 않다고 결론이 난다. Set에는 같은 값이 들어갈 수 없다는 사실에 위반된다. 따라서, structStringPtrGreater : public binary_function< const string*, const string*, bool > { bool operator()( const string* ps1, const string* ps2 ) const { return *ps1 > *ps2; // return *ps1 >= *ps2; ( x ) } }; 2.연관 컨테이너용 비교 함수는 같은 값에 대해 false를 반환 해야 한다.(항목21)

  26. map< int, string > m; m.begin()->first = 10; //error! Multimap< int, string > mm; mm.begin()->first = 20; //error also! 에러의 원인은 pair< const K, V > 즉, 키(KEY) 부분이 const K 이기 때문이다. 그런데, set과 multiset은 키가 const가 아니므로 변경이 가능하다. 하지만, 키 부분을 건들려면 컨테이너는 엉망이 되고 이 컨테이너는 예기치 못한 동작을 수행하며, 모든 책임을 여러분의 것이 된다는 사실! -내부 로직을 알 필요까진 없어요! 우리는 이러한 사실만 알고 그냥 주의하기만 합시다.- 3. set과 multiset에 저장된 데이터 요소에 대해 키(key)를 바꾸는 일은 피하자(항목22)

  27. map< int, Widget > m; m[1] = 1.50; m[2] = 3.67; Map의 [] 연산자는 “추가 아니면 갱신” 기능을 수행하도록 설계되어 있습니다. map< K, V > m; m[ k ] = v; 라는 표현 식이 있다면, 우선 해당 맵에k 가 있는 지 점검하고 없다면, k와 v가 한쌍으로 묶여서 맵에 새 로 추가된다. 만약 있다면, k와 맵핑된 값이 v로 갱신됩니다. m[1] = 1.50; 에서 m[1]은 사실 m.operator[](1) 과 같은 표현. 이 함수는 Wdiget에 대한 참조자를 반환 하도록 되어 있음. 이런 절차로 m[1] = 1.50은 operator[]가 기본 생성자를 사용해서 Widget 객체를 만 들고 1 과 함께 묶은 페어 객체를 만든 후, Widget 객체에 대한 참조자를 반환함. 그리고 참조자를 통해 1.50 값을 대입. 위의 식을다음과 같이 고쳐 쓸 수 있으며 typedef map< int, Widget > IntWidgetMap; m.insert( IntWidgetMap ::value_type( 1, 1.50 ) ); 이는 Widget 객체를 임시로 만드는 데 필요한 기본 생성자 함수, 소멸자 함수, 대입 연산자 함수가 호출 되지 않으므로 비용 절감 효과는 큼. 결론? 맵에 추가할 때는 insert. 갱신할 때는 operator[] 4. map ::operator[]와 map ::insert는 효율 문제에 주의하여 선택하자(항목24)

  28. 1. 해쉬 컨테이너 중에 가장 대표적인 것은 SGI 버전과 딩컴웨어(Dinkumware) 버전. 4. SGI 버전은 단일 연결 리스트로 저장하고 5.딩컴웨어 버전은 이중 연결 리스트로 저장한다는 사실. 그런데 해쉬 컨테이너의 특징이 무엇일까? 5.현재는 표준이 아니지만, 해쉬 컨테이너에 대해 충분히 대 비해 두자(항목25)

  29. 4. 반복자(Iterators) • 역시 지루해 지기 시작했다.

  30. vector<T> insert와 erase 시그너처 iterator insert( iterator position, const T& x ); iterator erase( iterator position ); iterator erase( iteratorrangeBegin, iteratorrangeEnd ); 알아둘 점은, const_iterator도 안되고 reverse_iterator와 const_reverse_iterator는 더더욱 안된다는 사실. 무조건 iterator! • const_iterator나 reverse_iterator, const_reverse_iterator도 좋지만 역시 쓸만한 것은 iterator이다. - 1(항목26) const_iterator base() iterator const_reverse_iterator reverse_iterator base() 변환 가능한 관계도

  31. iterator가 좋은 이유 • 어떤 형태의 insert와 erase 멤버 함수는 무조건 iterator만 넘겨야 한다. • const_iterator를 iterator로 암묵적으로 변환할 방법은 없으며 결국 일반성이 떨어저 효율에 대한 보장도 할 수 없음. • reverse_iterator를 iterator로 변환할 수는 있으나, 변환한 후에는 약간의 조정이 필요. • const_iterator나 reverse_iterator, const_reverse_iterator도 좋지만 역시 쓸만한 것은 iterator이다. - 2(항목26)

  32. typedefdeque< int > IntDeque; //typedef참 편리하죠? typedefIntDeque :: iteratorIter; typedefIntDeque :: const_iteratorConstIter; ConstIterci; //const_iterator라는 사실 명심하시고. … Iteri( ci ); //Error! 암묵적 캐스팅 불가! Iter I( const_cast< iter > ( ci ) ); //역시 Error! 캐스팅 또한 불가! 그럼 해결책은? 2.const_iterator를 iterator로 바꾸는 데에는 distance와 advance를 사용하자 (항목27) IntDeque d; Iteri( d.begin() ); //iteratori를 begin()으로 초기화 advance( i, distance< ConstIter >( i, ci ) ); //ci가 있는 곳을 i로 옮긴다. 아이구 간단하네. * 참고로 const_iterator를 iterator로 변환하는데 걸리는 시간은 선형 시간!

  33. reverse_iterator에서 base 멤버함수를 호출하면 iterator를 얻을 수 있다고 했었죠? 하지만, 정확하게 그러하지는 않는다는 사실 -_- vector< int > v; v.reserve( 5 ); for( inti = 1; i <= 5; ++i ) { v.push_back( i ); } vector< int > ::reverse_iteratorri = find( v.rbegin(), v.rend(), 3 ); vector< int >:: iteratori( ri.base() ); 그런데… 3.reverse_iterator에 대응되는 기점 반복자(base iterator)를 사용하는 방법을 정확하게 이해하자 - 1(항목28) rend() ri rbegin() 1 2 3 4 5 begin() i end() 해답은? vector< int > :: iteratori( (--ri).base() );

  34. 도대체 이따구로 만든 이유가 뭐지? 3.reverse_iterator에 대응되는 기점 반복자(base iterator)를 사용하는 방법을 정확하게 이해하자 - 1(항목28) 흐흐. 아무 이유 없습니다.그들이 또라이라서 그럽니다. rend() ri rbegin() 1 2 3 4 5 begin() i end() 보통 삽입 연산이 발생하면, 반복자가 가리키고 있는 요소의 앞에 추가하게 된다는 사실.

  35. 텍스트 파일을 string 객체에 복사한다면? ifstreaminputFile(“광호뱃살.txt”); string fileData( ( istream_iterator< char >( inputFile ) ), istream_iterator< char >() ); //inputFile을 읽어 fileData에 저장한다. 그럼 위의문제는? 4.문자 단위의 입력에는 istreambuf_iterator의 사용도 적절하다(항목29) Istream_iterator는 스트림 읽기 수행 시 operator >> 함수를 사용하며,이 함수는 공백 문자를 건너 뛰 어 버린다. 그럼 어떻게 해야 하나요? ifstreaminpuFile(“천재춘언.txt”); inputFile.unsetf( ios :: skipws ); //공백 문자를 건너 뛰지 못하도록 설정 String fileData( ( istream_iterator< char >( inputFile ) ), istream_iterator< char >() ); 하지만, 이보다 성능상 더 강력한 존재가 있으니 바로 istreambuf_iterator ifstreaminputFile(“승찬카리스마쌍꺼풀.txt”); string fileData( ( istreambuf_iterator< char >( inputFile ) ), istreambuf_iterator< char >() ); skipws플래그 설정 따윈 안해줘도 된답니다.

  36. 5. 알고리즘(Algorithms) • 아, 끝나냐 마냐?

  37. inttrasnmogrify( int x ); vector< int > values; … vector<int> results; transform( values.begin(), values.end(), results.end(), transmogrify ); results.end()에는 아무것도 없기 때문에 이는 버그를 유발합니다. 1. back_inserter를 사용하면 되겠죠. vector< int > results; transform( values.begin(), values.end(), back_inserter( results ), transmogrify ); 단,back_inserter가 반환하는 반복자는 push_back을 꼭 지원해야 합니다. 2. reserve로 메모리를 미리 확보해 둡니다. results.reserve( results.size() + values.size() ); transform( values.begin(), values.end(), results.end(), transmogrify ); 1. 알고리즘의 데이터 기록 범위는 충분히 크게 잡자(항목30)

  38. vector, string, deque, 혹은 c++배열에 대한 전체 정렬을 수행할 필요가 있을 때에는 sort나 stable_sort를 사용하자. • vector, string, deque, 혹은 c++배열에 대해 상위 n개의 요소만 순서에 맞추어 뽑아내고 싶다면partial_sort를 사용하자. • vector, string, deque, 혹은 c++배열에 대해 상위 n개의 요소만 뽑되 순소는 고려할 필요가 없다면, nth_element가 적합하다. • 표준 시퀀스 컨테이너가 있고, 이 컨테이너의 요소들을 어떤 기준에 만족하는 것들과 그렇지 않은 것들을 모아 구분하고 싶다면 partition 혹은 stable_partition을 찾자. • 사용하고 있는 데이터가 list인 경우에 partition과 stable_partition은 직접 사용할 수 있으며 sort와 stable_sort알고리즘 대신에 list :: sort 멤버 함수를 사용할 수 있다. • 표준 연관 컨테이너를 사용할 경우에는 모든 요소가 자동적으로 정렬된 상태에 있게 된다. • 아래는 (시간과 메모리)를 적게 먹는 순서대로 정렬해본 것. a. partition b. stable_partition c. nth_element d. partial_sort e. sort f. stable_sort 2. 정렬시의 선택 사항들을 제대로 파악해 놓자 - 1(항목31)

  39. partial_sort예제 boolqualityCompare( const Widge& lhs, const Widget& rhs ) { //정렬 기준에 대한 소스를 짭니다. } … partial_sort( widgets.begin(), widgets.begin() + 20, widgets.end(), qualityCompare ); 2. 정렬시의 선택 사항들을 제대로 파악해 놓자 - 2(항목31)

  40. 1. 탐색 알고리즘 ( 로그 시간 ) binary_search : 특정 원소의 존재 여부 판단 lower_bound : 값으로 들어온 값보다 작거나 같은 값을 가지는 원소의 위치 반환 upper_bound : 값으로 들어온 값보다 크거나 같은 값을 가지는 원소의 위치 반환 equal_range : 정렬 상태가 깨지지 않는 첫 번째, 마지막 위치 검색 2. 집합 조작 알고리즘 ( 선형 시간 ) set_union : 두 정렬된 범위의 합집합을 계산한다. set_intersection : 두 정렬된 범위의 교집합을 계산한다. set_difference : 두 정렬된 범위의 차집합을 계산한다. set_symmetric_difference : 두 정렬된 범위의 대칭 차집합을 계산한다. 3. 병합 정렬 알고리즘 ( 선형 시간 ) merge : 두 개의 정렬된 범위를 받아 합치고 다시 새로운 정렬된 범위를 생성 inplace_merge : 연속적으로 정렬된 범위를 병합 includes : 어떤 범위 안에 우리가 원하는 값이 있는지의 여부를 알아볼 때 3. 정렬된 범위에 대해 동작하는 알고리즘이 어떤 것들이 있는지 파악해 두자(항목34)

  41. copy – 첫 번째 원소로 시작하는 범위를 복사한다. copy_backward – 마지막 원소로 시작하는 범위를 복사한다. replace_copy – 원본의 범위를 복사한 후 replace를 실행한다. reverse_copy – 문서를 뒤바꾸는 동안 원소를 복사한다. replace_copy_if – 원본의 범위를 복사한 후 replace_if()를 실행 unique_copy – 원본의 범위를 복사한 후 unique() 실행 remove_copy – 원본의 범위를 복사한 후 remove() 실행 rotate_copy – 순서를 회전하는 동안 원소를 복사 remove_copy_if – 원본의 범위를 복사한 후 remove_if실행 partial_sort_copy – copy()와 partial_sort()가 결합된 상태 uninitialized_copy – 조사해 볼 것. 그런데.. copy_if는 표준 알고리즘에 없다는 사실. 적절한 라이브러리를 구해 서 사용하시길 바랍니다. ( HP사의 STL 추천 ) 4. copy_if를 적절히 구현해 사용하자(항목36)

  42. 1. Accumulate는 두가지 형태가 있습니다. a. 범위 내의 값의 합을 초기 값에 더한 결과를 반환. list< double > ld; double sum = accumulate( ld.begin(), ld.end(), 0.0 ); // 초기값은 반드시 float ! b. 초기값과 요약용 함수를 받아 동작하는 더 일반적인 버전. string ::size_typestringLengthSum( string ::size_typesumSoFar, const string& s ) { return sumSoFar + s.size(); } //참고 : size_type은 어떤 것을 셀 때 사용하는 타입 set< string > ss; string :: size_typelengthSum = accumulate( ss.begin(), ss.end(), 0, stringLengthSum ); * 추가로 범위 내의 수를 곱하는 multiplies라는 표준 함수자 클래스도 있다. float product = accumulate( vf.begin(), vf.end(), 1.0, multiplies< float >() ); 5. 범위 내의 데이터 값을 요약하거나 더할 때에는 accumulate나 for_each를 사용하자. - 1(항목37)

  43. 2. for_each는 accumulate와 다른 점이라면, a. accumulate는 “범위를 요약한다”는 느낌이 강한 반면, for_each는 “범위 내의 모든 요소에 대해 어떤 일을 한 다” 는 느낌을 준다. b. accumulate는 원하는 요약 결과를 바로 반환하지만, for_each는 요약 정보를 뽑아내야 함. class PointAverage : public unary_function< Point, void > { private: size_tnumPoints; double xSum; double ySum; public: PointAverage() : xSum( 0 ), ySum( 0 ), numPoints( 0 ) { … } void operator() ( const Point& p ) { ++numPoints; ySum += p.x; ySum += p.y; } Point result() const { return Point( xSum / numPoints, ySum / numPoints ); } }; List<Point> lp; …. Point avg = for_each( lp.begin(), lp.end(), PoinAverage() ).result(); 5. 범위 내의 데이터 값을 요약하거나 더할 때에는 accumulate나 for_each를 사용하자. - 2(항목37)

  44. 6. 함수자, 함수 객체, 함수, 기타 등등 • 아따, 빨랑끝내제는!

  45. 술어 구문 : BOOL값을 반환하는 함수 순수 함수 : 이 함수가 반환하는 값이 그 함수의 매개 변수에 종속된 함수. ex ) f가 순수 함수일 때 x, y가 객체이면, f( x, y )의 반환값은 x나 y의 값이 바뀔 때만 변할 수 있다. 술어 구문 클래스 : OPERATOR()가 술어 구문인 함수자 클래스. 1. 함구 객체는 값으로 전달되게 해라. (항목38)2. 술어 구문은 순수 함수로 만들자. (항목39) 3. less<T> 는 operator< 의 의미임을 알고 있자. ( 항목 42 )

  46. 7. STL 프로그래밍을 더 재미있게 해주는 팁 모음 • zzz…

  47. 그 이유? 1. 알고리즘은 직접 만든 루프보다 자주 훨씬 효율적이다. 2. 에러가 일어날 가능성이 더 적다. 3. 훨씬 깨끗하고 간명한 코드로 유지 보수가쉽다. 4. 알고리즘 제작자는 그 내부를 충분히 파악하고 있으므로 최적화가 되 어 있을 가능성이 높다. 5. 알고리즘 제작자는 컴퓨터 공학적인 알고리즘을 사용한다. 1. 어설프게 손으로 작성한 루프보다는 알고리즘이 더 낫다.(항목 43) for( list<Widget> :: iteratori = lw.begin(); i!= lw.end(); ++i ) { i->redraw(); } for_each(lw.begin(), lw.end(), mem_fun_ref( &Widget ::redraw ) );

  48. 그 이유? 1. 멤버 함수가 더 빠르다. 2. 멤버 함수는 해당 컨테이너와 더 잘 맞물려 있다는 점. 3. 표준 연관 컨테이너의 멤버함수는 선형 시간이 아닌 로그시간 4. 맵이나멀티맵 같은 경우 키 부분을 다룰 필요가 없다. 5. 알고리즘으로 객체 제거시, remove, remove_if, unique 등으로 호출해 서는 안되고 erase를 반드시 붙여주어야 한다는 것. 2. 같은 이름을 가진 것이 있다면 일반 알고리즘 보다 멤버 함수가 더 낫다.(항목 44) Set< int > s; … Set<int> :: iterator I = s.find( 727 ); //아하, 보기에도 더 편하구먼! Set<int > :: iterator I = find( s.begin(), s.end(), 727 );

  49. 3. count, find, binary_search, lower_bound, upper_bound, 그리고 equal_range를 제대로 파악해 두자. (항목 45)

  50. STL 관련 웹 사이트 SGI STL : http://www.sgi.com/tech/stl/ STLport : http://www.stlport.org/ Boost : http://www.boost.org/ 4. 알고리즘 매개 변수로 함수 대신 함수 객체가 괜찮다.(항목 46)5. 쓰기전용 코드는 만들지 말자. (항목 47)6. 에러 메시지 분석 능력을 키우자. ( 항목 48 )

More Related