1. Generic Programming and the C++ Standard Library

  1. Iterators

다음 코드의 4가지 문제점을 찾아 보라.

int main() 
{
  vector<Date> e;
  copy( istream_iterator<Date>( cin ),
        istream_iterator<Date>(),
        back_inserter( e ) );
  vector<Date>::iterator first =
        find( e.begin(), e.end(), "01/01/95" );
  vector<Date>::iterator last =
        find( e.begin(), e.end(), "12/31/95" );
  *last = "12/30/95";
  copy( first,
        last,
        ostream_iterator<Date>( cout, "\n" ) );
  e.insert( --e.end(), TodaysDate() );
  copy( first,
        last,
        ostream_iterator<Date>( cout, "\n" ) );
}
  • last가 역참조 가능하지 않을 수 있다.
  • first가 last보다 앞일 수 있다.
  • –e.end()가 불가능할 수 있다.
  • first, last가 무효화된 반복자일 수 있다.

무효한 반복자를 절대 역참조하지 말라. 반복자를 쓸 때는 4가지를 명심하라.

  • 역참조 가능한가?
  • 반복자의 생애 주기가 유효한가?
  • 반복자의 쌍이 올바른 구간인가?
  • 올바른 내장 조작을 하고 있는가?

2. Case-Insensitive Strings-Part 1

대소문자를 구별하지 않는 문자열 클래스를 만들어 보라.

  • 대소문자 구별은 무엇을 말하는가? 언어에 따라 대소문자 구분이 없을 수도 있다.
  • 이는 std::char_traits을 상속받아 커스터마이징하면 된다.
  • 대소문자를 구별하는 특성을 두는 것이 좋을까? 다른 문자열 클래스와 비교할 경우도 생각해야 한다.

3. Case-Insensitive Strings-Part 2

Item 2에서 만든 ci_string을 평가해 보자.

  • std::char_traits<char>를 상속받아 ci_char_traits를 만드는 것이 안전할까? char_traits는 다형성을 가지지 않으므로 리스코프 대입 이론을 따르진 않지만 works-like-A에 들어가므로 이 상속은 안전하다.
  • ci_string에 std::cout << 을 쓰면 왜 컴파일이 실패하는가? std::basic_string의 operator<<는 템플릿화되어 있기 때문에 operator<<를 ci_string에 쓰면 출력 스트림을std::basic_ostream<char, ci_char_traits>으로 간주한다. 이는 std::cout과 다르다. 해결법은 operator<<()와 operator>>()을 직접 정의하는 것이다.
  • std::string과 operator+, operator+=, operator=도 직접 정의하는 수밖에 없다.

4. Maximally Reusable Generic Containers-Part 1

이 컨테이너를 어떻게 더 유연하게 만들 것인가?

template<typename T, size_t size> 
class fixed_vector
{
public:
  typedef T*       iterator;
  typedef const T* const_iterator;
  iterator       begin()       { return v_; }
  iterator       end()         { return v_+size; }
  const_iterator begin() const { return v_; }
  const_iterator end()   const { return v_+size; }

private:
  T v_[size];
};

해결책은 Item 5에서 알아보자.

5. Maximally Reusable Generic Containers-Part 2

Item 4의 아래 해결책은 어떤 문제가 있는가?

template<typename T, size_t size> 
class fixed_vector
{
public:
  typedef T*       iterator;
  typedef const T* const_iterator;
  fixed_vector() { }

  template<typename O, size_t osize>
  fixed_vector( const fixed_vector<O,osize>& other )
  {
    copy( other.begin(),
          other.begin()+min(size,osize),
          begin() );
  }

  template<typename O, size_t osize>
  fixed_vector<T,size>&
  operator=( const fixed_vector<O,osize>& other )
  {
    copy( other.begin(),
          other.begin()+min(size,osize),
          begin() );
    return *this;
  }

  iterator       begin()       { return v_; }
  iterator       end()         { return v_+size; }
  const_iterator begin() const { return v_; }
  const_iterator end()   const { return v_+size; }

private:
  T v_[size];
};

일단 위의 두 함수는 복사 생성자와 복사 대입 연산자가 아니다. 이는 템플릿 생성자이다. 이는 이동 생성자와 이동 대입 연산자에 대해서도 둘 다 해당된다.

사용성을 증가시키기 위해서는 다른 타입의 컨테이너로부터 상속받을 수 있게 하는 것도 고려할 필요가 있다. 또한 가변 크기도 지원하는 것을 생각해볼 수 있다. 표준 라이브러리에서는 STL 알고리즘인 std::copy, std::move를 사용한다.

대입 동작이 예외에서 안전한지도 중요한 요소이다. 대입 동작이 원자적이 아니라면 이는 예외에서 안전하지 않다. 이는 내부 버퍼를 동적으로 할당되는 배열로 바꾸고 소멸자에서 이를 해제하게 해서 해결할 수 있다. 예외로부터의 안전성은 중요한 설계 요소이다.

6. Temporary Objects

여기에 불필요한 임시 오브젝트는 몇 개가 있는가?

string FindAddr( list<Employee> emps, string name ) 
{
  for( list<Employee>::iterator i = emps.begin();
       i != emps.end();
       i++ )
  {
    if( *i == name )
    {
      return i->addr;
    }
  }
  return "";
}
  • 인자를 값으로 넘기지 말고 const& 로 넘겨라.
  • 바뀌지 않는 값이라면 미리 계산해 놓으라.
  • 후위 증가 연산자는 전위 증가 연산자를 이용해 구현하라.
  • 전위 증가 연산자를 선호하라.
  • 비교 시에 임시 오브젝트가 생성되는 것을 주의하라.
  • 오브젝트 생애 주기를 인지하라. 절대 지역 자동 주기 오브젝트의 포인터나 참조를 리턴하지 마라.

7. Using the Standard Library (or, Temporaries Revisited)

Item 6의 문제점이 다음 코드에서는 얼마나 고쳐졌는가?

string FindAddr( const list<Employee>& emps, 
                 const string&         name )
{
  list<Employee>::const_iterator end( emps.end() );
  for( list<Employee>::const_iterator i = emps.begin();
       i != end;
       ++i )
  {
    if( i->name == name )
    {
      return i->addr;
    }
  }
  return "";
}
  • 코드를 직접 만드는 대신 표준 라이브러리의 코드를 재사용하라. 더 빠르고, 쉽고, 안전하다.