Effective C++/Effective STL
[Effective STL] 42. less<T>
김디트
2025. 1. 10. 10:56
728x90
항목 42. less<T>는 operator<의 의미임을 꼭 알아두자
less<T>의 기본 동작
- 아래처럼 기본적으로 무게로 정렬되는 Widget이 있다.
class Widget
{
public:
...
size_t weight() const;
size_t maxSpeed() const;
// 무게로 정렬되는 operator<
bool operator<(const Widget& lhs, const Widget& rhs)
{
return lhs.weight() < rhs.weight();
}
};
- 헌데, 최대 속도(maxSpeed)를 가지고 정렬하는 multiset<Widget>을 만들고 싶다면?
- multiset<Widget>의 기본 비교 함수는 less<Widget>이다.
- 하지만, less<Widget>은 기본적으로 Widget 객체 두 개에 대해 operator<를 호출하여 동작한다.
문제 해결하기(잘못된 예)
- 템플릿 특수화로 less<Widget>과 operator<의 관계를 끊어버린다.
// 템플릿 특수화로 처리한다.
template<>
struct std::less<Widget>
{
bool operator()(const Widget& lhs, const Widget& rhs) const
{
return lhs.maxSpeed() < rhs.maxSpeed();
}
};
- 일반적으로 std 내의 STL 컴포넌트를 수정하는 일은 금지되어 있다.
- 하지만 일부 경우에는 약간의 처리(tinkering)은 허용된다.
- 사용자 정의 타입에 대해서는 std 템플릿을 조정해서 한정할 수 있다.
- 그래서 위 코드는 컴파일 에러가 발생하지 않는다.
- STL 컴포넌트를 수정해야 하는 상황 예시(C++98 한정)
-
// shared_ptr이 표준이 아니었을 때는 이런 처리가 필요했다. namespace std { tempalte<typename T> struct less<shared_ptr<T>> { bool operator()(const shared_ptr<T>& a, const shared_ptr<T>& b) const { // 단지 less에 T*가 들어왔을 때처럼 동작하게 래핑했을 뿐이다. return less<T*>()(a.get(), b.get()); } } }
- 기본적으로 사용자는 less가 operator<와 동일한 동작을 하는 것으로 기대한다.
- 하지만 템플릿 특수화 버전은 그걸 가볍게 무시해버리며, 직관성이 무너진다.
multiset<Widget> widgets; // 누가 봐도 operator<로 정렬될 것 같지만....
// 템플릿 특수화 버전의 less로 정렬됨을 추론할 수가 없다.
직관적으로 문제 해결하기
- less 이외의 함수 객체로 비교를 수행하게 한다.
struct MaxSpeedCompare
{
bool operator()(const Widget& lhs, const Widget& rhs) const
{
return lhs.maxSpeed() < rhs.maxSpeed();
}
};
multiset<Widget, MaxSpeedCompare> widgets; // 비교를 수행할 함수 객체를 사용하여 선언
- 람다를 사용할 수도 있다.
// 물론 람다를 사용할 수도 있다.
auto compare = [](const Widget& lhs, const Widget& rhs)
{
return lhs.maxSpeed() < rhs.maxSpeed();
};
multiset<Widget, decltype(compare)> widgets(compare); // 인자로 람다를 넘겨줘야 한다.
728x90