블로그 이미지
이태원에서 사는 다섯식구의 무직 가장. 흰둥에미

카테고리

분류 전체보기 (184)
Itaewon (2)
ryu's?? (1)
20121210이전 (20)
20130827이전 (147)
soo'study (13)
Total34,023
Today5
Yesterday5

항목9. 데이터를 삭제할 때에도 조심스럽게 선택할 것이 많다.

- 컨테이너마다 조금씩 다른 방식이 존재

- 1963의 값을 가진 데이터를 지우는 경우.

  - 연속 메모리 컨테이너의 경우 erase-remove 합성문을 사용.

    Container<int> c;

    c.erase( remove(c.begin(), c.end(), 1963), c.end() );

  - list의 경우

    c.remove(1963);

  - 표준 연관 컨테이너의 경우. (상등성(equality)가 아닌 동등성(equivalence)에 기반하고 있다는 이점이 있음)

    c.erase(1963);

- 술어 구문(predicate)이 true를 반환하는 요소를 지우는 경우.

  bool badValue(int x);

  - 연속 메모리 컨테이너의 경우 erase-remove_if 합성문을 사용.

    c.erase( remove_if(c.begin(), c.end(), badValue), c.end() );

  - list의 경우

    c.remove_if(badValue);

  - 표준 연관 컨테이너의 경우. (상등성(equality)가 아닌 동등성(equivalence)에 기반하고 있다는 이점이 있음)

    루프를 통해 이터레이터를 이용함은 조심해야 함.(요소 삭제 시, 모든 반복자들이 한꺼번에 무효화 되기 때문에)

    for(Container<int>::iterator iter=c.begin(); iter != c.end(); )

    {

        if(badValue(*i))

            c.erase(iter++);

        else

            ++iter;

    }

  - erase에는 iter의 현재값을 넘기고, erase가 수행되기 전에 iter는 증가 되므로, 무효화전에 다음의 요소를 가지고 있을 수 있음

    * 시퀀스 컨테이너의 경우 iter = c.erase(iter);와 같은 코드로 반복자를 유효하게 관리 할 수 있음.


  결론 1. 컨테이너에서 특정한 값을 가진 객체를 모두 없애려면

    - vector, string, deque라면, erase-remove 합성문

    - list라면, list::remove

    - 표준 연관 컨테이너라면, erase 멤버 함수.

  결론 2. 컨테이너에서 특정한 술어 구문을 만족하는 객체를 모두 없애려면

    - vector, string, deque라면, erase-remove_if 합성문

    - list라면, list::remove_if

    - 표준 연관 컨테이너라면, remove_copy_if와 swap을 쓰든지,

      컨테이너 내부를 도는 루프에서 erase를 호출하면서 erase에 넘기는 반복자를 후위 증가 연산자로 증가

  결론 3. 루프 안에서 무엇인가를 하려면(객체 삭제도 포함해서)

    - 표준 시퀀스 컨테이너면, 컨테이너 요소를 하나씩 사용하는 루프내에서, erase를 호출할 때마다 반환값으로 반복자를 업데이트

    - 표준 연관 컨테이너라면, 컨테이너 요소를 하나씩 사용하는 루프내에서,

      erase를 호출하면서 erase에 넘기는 반복자를 후위 증가 연산자로 증가

  

신고
Posted by 흰둥에미

항목8. auto_ptr의 컨테이너는 절대로 만들지 말자.

- auto_ptr의 참조카운팅을 하나만 지원, 즉 auto_ptr의 객체가 대입, 복사등이 이루어지면, 소유권이 바뀌어 버림.

- STL의 많은 기능이나 동작들은 객체 복사를 통해 이루어짐.

- 따라서 COAP(Container Of Auto Ptr)은 절대 금지.

  auto_ptr<Object> pObj1(new Object);
  auto_ptr<Object> pObj2(pObj1);        // pObj1에 있던 객체를 pObj2가 가리키고, pObj1은 NULL로 세팅됨.
  ...
  pObj1 = pObj2;                                // pObj2에 있던 객체를 pObj1가 가리키고, pObj2은 NULL로 세팅됨.

  - 위와 같은 상황 이전에, 코드 이식성이 보장되지 않음.

  - 스마트 포인터가 필요하다면, STL고 잘 섞어 쓸 수 있는 스마트 포인터를 구해서 써야함.

신고
Posted by 흰둥에미

항목7. new로 생성한 포인터의 컨테이너를 사용할 때에는 컨테이너가 소멸되기 전에 포인터를 delete하는 일을 잊지 말자.

- STL 컨테이너는 typedef로 정의된 value_type을 통해 관리하고 있는 데이터의 타입을 알려줌.

  new로 할당된 객체를 가리키는 포인터를 컨테이너에 담는 경우에는 직접 객체의 소멸이 이루어져야 함.

  for루프로 객체 소멸시키는 코드를 for_each로 바꾸기 위해선, 객체 소멸을 위해 delete하는 부분이 함수 객체(또는 함수)로 바꿔야함.

  template<typename T>
  struct DeleteObject; public unary_function<const T*, void>
  {
       void operator () (const T* ptr) const
       {
           delete ptr;
       }
   };
   for_each(vop.begin(), vop.end(), DeleteObject<Object>());   

   - 위와 같은 코드에서 가상 소멸자 없이 public 상속한 클래스에 대해서 DeleteObject<Parent>형으로 수행시, 문제 발생할 수 있음.

     템플릿 대상을 DeleteObject에서 operator()로 옮기면 해결 가능.

  

  struct DeleteObject
  {
       template<typename T>
       void operator () (const T* ptr) const
       {
           delete ptr;
       }
   };
   for_each(vop.begin(), vop.end(), DeleteObject());   

   - 템플릿 대상을 DeleteObject에서 operator()로 옮기면서, 어느 정도의 안전성은 확보되었으나,

     위 for_each문을 호출하지 못한다면(예외 등에 의해) 메모리 누수가 발생할 수 있음.

   - 결과적으로 포인터의 컨테이너를 쓰려거든, 괜찮은 상황이라면 스마트 포인터의 컨테이너로 바꾸라는 것

   - 단 컨테이너 자체를 스마트 포인터로 만들려고 하는 것은 잘못된 생각임.


신고
Posted by 흰둥에미

항목6. C++ 컴파일러의 어이없는 분석 결과를 조심하자.

- ifstream dataFile("data.dat");

  list<int> data( istream_iterator<int>(dataFile), istream_iterator<int>() );

  - 위 코드는 아래와 같이 컴파일러에 의해 해석됨.

    - 첫째 매개 변수는 dataFile이란 이름을 가지고 있음, 타입은 istream_iterator<int>,

    dataFile을 둘러싸고 있는 괄호는 불필요한 것이므로 컴파일러가 무시

    - 둘째 매개 변수는 이름을 가지고 있지 않음, 타입은 아무 것도 받아들이지 않고 istream_iterator<int>를 반환하는 함수포인터

  따라서 아래와 같이 해줘야 원하는 동작이 가능

  list<int> data( (istream_iterator<int>(dataFile)), istream_iterator<int>() );

  - 형식 매개 변수 선언을 괄호로 둘러싸는 것은 맞지 않지만, 함수의 매개 변수는 괄호로 둘러싸도 됨.

  - 하지만 위와 같이 했을 때, 컴파일러에 따라 선언을 거부하기도 함.


  결론적으로 아래와 같이(익명 객체 선언을 istream_iterator에 쓰지말고, 각 반복자 객체의 이름을 만들어 넣어줌) 사용하면 해결.

  ifstream dataFail("data.dat");

  istream_iterator<int> dataBegin(dataFile);

  istream_iterator<int> dataEnd;

  list<int> data(dataBegin, dataEnd);

신고
Posted by 흰둥에미

항목4. size()의 결과를 0과 비교할 생각이라면 차라리 empty를 호출하자.

- empty는 모든 표준 컨테이너에 대해 상수 시간에 수행되지만, 몇몇 제품에서 구현된 list 클래스에서 size가 선형 시간에 수행됨.

  list의 splice란 유용한 함수의 상수 시간 동작을 위해, size를 구할 때 매번 요소들의 개수를 카운트 하게 구현된 경우가 있음.


항목5. 단일 요소를 단위로 동작하는 멤버 함수보다, 요소의 범위를 단위로 동작하는 멤버 함수가 더 낫다.

- v1.assign(v2.begin()+v2.size()/2, v2.end()); --> v1을 v2의 뒤쪽 절반과 똑같이 만드는 코드.

  assign이라는 함수는 표준 시퀀스 컨테이너(vector, string, deque, list)라면 모두 사용 가능.

  컨테이너의 내용을 완전히 교체하고 싶다면 assign을 떠올리면 좋음.

  범위 멤버 함수를 사용하지 않고, 위의 코드를 작성하기 위해선 루프가 필요해짐.

  삽입 연산자(inserter, back_inserter, front_inserter)를 사용해서 복사 범위를 지정하는 copy는 거의 모두 범위 멤버 함수로 변경가능.

  copy(v2.begin() + v2.size() / 2, v2.end(), back_inserter(v1)); --> v1.insert(v1.end(), v2.begin() + v2.size() / 2, v2.end());

  위와 같이 범위 멤버 함수를 사용하면 코드가 대개 짧고, 명확하고 간결한 의미를 전달함.

  vector의 경우 범위 함수가 아닌 단일 요소 버전의 함수를 사용할 경우,

  1. 불필요한 함수 호출의 증가, 2. 매번 갱신되는 컨테이너내의 메모리 이동, 3. 메모리 할당(vector의 내부 복사 동작.)

  위 세가지 단점이 있다. 그 외의 표준 시퀀스 컨테이너들도, 모두는 아니지만 특정 단점을 물고 들어가게 되므로 범위 함수를 사용하자.

  효율을 떠나서도, 간결한 코드는 유지 보수성을 높여줄 수 있음.

  - 범위 생성

  - 범위 삽입

  - 범위 삭제

  - 범위 대입

신고
Posted by 흰둥에미

항목3. 복사는 컨테이너 안의 객체에 맞게 비용은 최소화하며, 동작은 정확하게 하자.

- 컨테이너의 객체는 복사되어 들어가고, 복사되어 나오는게 STL 방식

  연속 메모리 컨테이너의 객체에 삽입, 삭제를 했을 때, 내부의 객체들은 복사를 통해 밀려나고 밀려옴.

  특히 복사 생성자와 복사 대입 연산자가 사용됨(아래 두 시그너쳐로 직접 선언하지 않으면, 컴파일러가 자동 생성 해줌.)

  class Object {

  public:

      Object(const Object&);

      Object& operator = (const Object&);

      ...

  };

  따라서 비용이 큰 객체를 담는 컨테이너를 사용하면 수행 성능에 병목을 일으킴

  상속된 객체의 경우 복사 동작 중에 슬라이스 현상이 발생할 수 있음.(파생 클래스 객체를 기본 클래스 객체의 컨테이너에 삽입할 때)

  class Object { ... };

  class Cup : public Object { ... };

  vector<Object> objVec;

  Cup cup;

  objVec.push_back(cup);  --> 슬라이스..


  - 따라서 객체의 컨테이너가 아닌 포인터의 컨테이너를 사용하면 위의 문제들이 해결된다.

    포인터의 컨테이너도 나름대로의 문제점을 가지고 있는데, 스마트 포인터를 이용하여 문제 해결.

    STL의 컨테이너가 많은 복사 동작이 있는 것은 사실이지만, 동적으로 사이즈를 늘려가기에 불필요한 복사 생성은 피하도록 설계.


신고
Posted by 흰둥에미

항목2. "컨테이너에 독립적인 코드"라는 환상을 조심하자.

  - STL은 일반화에 기초를 두고 만든 프로그래밍 장치

    여러 가정에 의해, STL 컨테이너의 일반화를 고려하였을 때, 지금이 적절하게 각 컨테이너의 구조나 특성에 따라 분류되었음.

    - push_back, push_front는 시퀀스 컨테이너만 지원, count와 lower_bound는 연관 컨테이너만 지원

    - 시퀀스 컨테이너에 어떤 객체를 insert하면 삽입한 위치에 있지만, 연관 컨테이너는 자체의 정렬 방식에 맞게 옮김.

    - erase의 경우 시퀀스 컨테이너는 반복자가 새로 반환되지만, 연관 컨테이너에 대해 호출하면 아무 것도 반환되지 않음.

    - 시퀀스 컨테이너 끼리의 일반화를 가정하더라도, 포기해야 되는 기능이 너무 많아짐.

    - deque의 insert는 모든 반복자를 무효화, capacity 미지원

    - vector의 insert는 모든 포인터와 참조자를 무효화 한다고 봐야 함.

    - vector만이 데이터를 C인터페이스에 넘길 수 있음.

    - vector<bool>은 절대로 vector처럼 동작하지 않을 뿐더러, 실제로 bool데이터가 저장되지 않음.

    - list는 상수 시간에 요소 삽임과 삭제가 이루어짐.


  - 경우에 따라 수시로 컨테이너 타입을 바꿀 수 밖에 없는 입장이라면, STL컨테이너를 "캡슐화"

    아래와 같이 구현할 경우, typedef된 컨테이너를 바꾸고, 필요한 부분 약간을 수정해 주는 걸로 컨테이너 변경이 됨.

    하지만 캡슐화의 효과는 지극히 lexical

    class Object { ... };

    typedef vector<Object> ObjContainer;

    typedef vector<Object>::iterator OCIter;

    ObjContainer objVec;

    ...

    OCIter iter = objVec.begin();

    클래스를 이용한 캡슐화

    class ObjectList

    {

    private:

        typedef list<Object> ObjContainer;

        typedef ObjContainer::iterator OCIter;

        ObjContiner _objects;

    ... ...

    };

    위의 경우, 컨테이너가 사용되는 특성에 따라 적합한 컨테이너로 변경이 좀 더 용이.


- 컨테이너를 일반화 할 것이 아니라, 컨테이너 독립적인 코드를 작성하여, 좀 더 적절한 컨테이너를 사용하기에 용이하도록 하자.


신고
Posted by 흰둥에미

- STL은 반복자, 알고리즘, 함수 객체 등을 모아 놓은 것. (컨테이너도 있음)

항목 1. 적재적소에 알맞은 컨테이너를 사용하자

  - 표준 STL 시퀀스 컨테이너 : vector, string, deque, list

    표준 STL 연관 컨테이너 : set, multiset, map, multimap

    비표준 시퀀스 컨테이너 : slist(단일 연결 리스트)와 rope(대용량 string)

    비표준 연관 컨테이너 : hash_set, hash_multiset, hash_map, hash_multimap

                                    표준 연관 컨테이너를 확장하여 해쉬 테이블을 쓸 수 있도록 한 변형 클래스가 많이 있음.

    string 대신 사용되는 vector<char>

    표준 연관 컨테이너 대신 사용되는 vector : vector가 수행 속도나 크기 면에서 표준 연관 컨테이너보다 나은 경우가 있음.

    STL에 속하지 않는 표준 컨테이너 : 배열(C++배열), bitset, valarray, stack, queue, priority_queue

                                                    STL컨테이너 보다 배열이 더 나은 경우

                                                    vector<boo>대신 bitset을 써야 하는 이유

                                                    C++배열은 STL알고리즘과 문제없이 사용 가능(배열의 반복자로 포인터를 쓸 수 있음)


  - 연속 메모리 컨테이너(배열 기반) : 각 메모리 단위(chunk)는 하나 이상의 요소를 담고 있음.

                                                    삽입, 삭제에 의해 같은 메모리 단위에 있는 다른 요소들은 앞 혹은 뒤로 밀려남.

                                                    이러한 밀어내기는 수행 성능에 발목을 잡고, 예외 안전성에도 영향을 미침

                                                    vector, string, deque, rope

    노드 기반 컨테이너 : 동적 할당된 하나의 메모리 단위에다가 하나의 요소만을 지정

                                  list나 slist가 노드 기반, 표준 연관 컨테이너 모두(전형적으로 균형 트리로 구현)

                                  비표준인 해쉬 컨테이너도 노드 기반


  - 컨테이너의 선택시 고려 사항

    1. 랜덤 액세스가 가능해야 한다면 시퀀스 컨테이너

    2. 컨테이너 내의 요소들의 순서 결정에 직접 관여하고 싶다면, 해쉬 컨테이너는 피해야 함.

    3. 표준 C++에 포함된 컨테이너를 원한다면, 해쉬 컨테이너, slist, rope는 쓸 수 없음.

    4. 임의 접근 반복자가 필요하다면 vector, deque, string, rope를 쓰고, 양방향 반복자가 필요하면 slist, 해쉬 컨테이너는 피해야 함.

    5. 요소 삽입 삭제시 다른 컨테이너 요소들이 밀려나는 일이 없어야 한다면, 연속 메모리 컨테이너는 피해야 함.

    6. 컨테이너 내의 데이터가 C의 데이터 타입과 메모리 배열에 구조적으로 호환되어야 한다면, vector 뿐

    7. 탐색 속도가 중요하다면, 해쉬 컨테이너, 정렬된 vector, 표준 연관 컨테이너 순으로 고려해 볼 것.

    8. 컨테이너의 참조 카운팅이 신경쓰인다면, string, rope를 피하고, vector<char>로 대신 할 것.

    9. 트랜잭션적인 삽입, 삭제가 여러 개의 요소에 대해 이루어져야 한다면, list를 선택.

       트랜잭션적인 동작은 예외 안전성을 추가하는데 중요.

   10. 반복자, 포인터, 참조자가 무효화되는 일을 최소화하려면, 노드 기반 컨테이너를 선택

   11. 임의 접근 반복자를 지원하는 시퀀스 컨테이너이면서, 요소 삭제가 일어나지 않고 요소 삽입이 컨테이너의 끝에서만 일어나는 한,

       포인터와 참조자가 무효화되지 않는 것은 deque(요소 삽입이 끝에서 일어날 때 반복자만 무효화 됨.)


신고
Posted by 흰둥에미

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

티스토리 툴바