스토리텔링 개발자

[Effective STL] 20. 컨테이너 비교 함수 객체 본문

개발/Effective STL

[Effective STL] 20. 컨테이너 비교 함수 객체

김디트 2024. 12. 3. 11:11
728x90

항목 20. 포인터를 저장하는 연관 컨테이너에 대해서는 적합한 비교(비교 함수 객체) 타입을 정해주자

 

 

 

연관 컨테이너 문제
set<string*> ssp;
ssp.insert(new string("Anteater"));
ssp.insert(new string("Wombat"));
ssp.insert(new string("Lemur"));
ssp.insert(new string("Penguin"));

for(set<string*>::const_iterator i = ssp.begin() ; i != ssp.end() ; ++i)
{
    cout << *i << endl; // 알파벳 순으로 동물 이름이 출력되지 않을까? 하지만..
}
  • set에 들어있는 값은 포인터이기에 출력물은 16진수 네 개가 한 줄씩 나올 뿐이다.
  • 아래와 같이 한다면?
// ssp의 문자열을 cout으로 복사한다. 하지만..
copy(ssp.begin(), ssp.end(), ostream_iterator<string>(cout, "\n")); // 컴파일 에러!
  • ostream_iterator<string>은 string* 타입 요소의 반복자가 아니므로 컴파일 에러가 발생한다.
  • 아래와 같이 한다면?
for(set<string*>::const_iterator i = ssp.begin() ; i != ssp.end() ; ++i)
{
    cout << **i << endl; // 이제 문자열이 출력될 것이다. 하지만..
}
  • 일반적으로는 알파벳 순서대로 나오지 않는다..
    • 포인터 값을 기준으로 정렬되어 나오기 때문이다.

 

 

 

비교 함수 객체 정의
set<string*> ssp;
// 실은  위 코드는 아래의 코드를 생략하고 쓴 것이다.
set< string*, less<string*>/*비교 함수 객체*/, allocator<string*>/*할당자*/ > ssp;
// string*을 알파벳 순으로 늘어서도록 할 때는 기본 비교 함수 객체(less)를 쓸 수 없다.
// 커스텀 해보자.

/*
struct StringPtrLess : public binary_function<const string*, const string*, bool>
{
    bool operator()(const string* ps1, const string* ps2) const
    {
        return *ps1 < *ps2;
    }
};
모던 c++에서는 binary_function이 삭제되었으므로 아래와 같이 쓰자.
*/
struct StringPtrLess
{
    bool operator()(const std::string* ps1, const std::string* ps2) const
    {
        return *ps1 < *ps2;
    }
};

// typedef set<string*, StringPtrLess> StringPtrSet;
// 대신 using을 사용하자.
using StringPtrSet = std::set<std::string*, StringPtrLess>;
StringPtrSet ssp;

for(StringPtrSet::const_iterator i = ssp.begin() ; i != ssp.end() ; ++i)
{
    // 이제 알파벳 순으로 출력된다.
    cout << **i << endl;
}
  • for_each로 for 문을 더 깔끔하게 바꿔보자.
void print(const string* ps)
{
    cout << *ps << endl;
}

for_each(ssp.begin(), ssp.end(), print);

// 아래처럼 람다를 사용해도 된다.

for_each(ssp.begin(), ssp.end(), [](const string* ps)
{
    cout << *ps << endl;
});
  • transform과 ostream_iterator로 더 깔끔하게 만들어보자.
// 이 타입의 함수 객체에 T*이 넘겨지면, const T&를 반환한다.
struct Dereference
{
    template<typename T>
    const T& operator()(const T* ptr) const
    {
        return *ptr;
    }
};

// ssp의 요소를 역참조용 함수 객체(Dereference())를 통해 변환하고, 그 결과를 cout으로 보낸다.
transform(ssp.begin(), ssp.end(), ostream_iterator<string>(cout, "\n"), Dereference());

 

 

 

비교 '타입'(comparison type)
  • 아무튼 핵심은, 포인터를 담는 연관 컨테이너는 포인터 값을 기준으로 정렬된다는 것을 기억해야 한다는 것이다.
    • 보통은 포인터를 기준으로 정렬되는 것을 원하지 않을 것이다.
    • 그러므로 비교 타입으로 동작할 함수 객체 클래스를 거의 항상 만들어야 할 것이다.
  • 비교 '타입'? 그냥 set 요소 비교 함수를 만들면 되는 거 아닌가?
// 타입이 아니라 함수로 만들어 본다.
bool stringPtrLess(const string* ps1, const string* ps2)
{
    return *ps1 < *ps2;
}
set<string, stringPtrLess> ssp; // 하지만 컴파일이 안 된다.
  • 하지만, 템플릿에 들어가는 세 매개 변수(타입, 비교자, 할당자)는 타입(type)이어야 한다는 제약이 있다.
  • 결국 필요해진다면, 포인터를 담는 연관 컨테이너를 위한 범용 비교 타입이 있으면 좋을 것이다.
struct DereferenceLess
{
    template<typename PtrType>
    bool operator()(PtrType pT1, PtrType pT2) const
    {
        return *pT1 < *pT2;
    }
};

// 사용만 하면 된다.
set<string*, DereferenceLess> ssp;

 

 

 

추가
  • 연관 컨테이너에 포인터가 아니라 포인터처럼 동작하는 것이 들어가도 마찬가지이다.
    • 예컨대 스마트 포인터, 반복자 같은 것들.
  • 다행히 위에서 만든 DereferenceLess 타입은, 이들에도 잘 동작한다.
728x90
Comments