항목 40 : 함수자 클래스는 어댑터 적용이 가능하게(adaptable) 만들자
◾ 어댑터?
이미 구현된 기능은 그대로 활용하고, 인터페이스만 변형시킨 것.
구현된 기능의 일부를 막거나 고정시켜서 사용할 수 있지만 없는 기능을 만들수는 없다.
컴포넌트 어댑터 : 스택, 큐, 우선순위 큐
반복자 어댑터 : 역방향 반복자
함수 객체 어댑터 : 부정자, 바인더, 함수 포인터 어댑터
함수 객체 어댑터 적용이 가능하려면 typedef 몇 가지를 가지고 있어야 한다.
앞선 예제로 not2, ptr_fun을 사용했던 적이 있는데, ptr_fun이 바로 단항 함수 포인터를 단항 함수 객체로 바꿔서 반환해주는 역할을 한다. 단항 함수 객체로 바뀔 때, typedef 몇 가지가 추가되어서 함수 객체 어댑터인 not2(부정자)에 사용이 가능했던 것이다.
보통 unary_function, binary_function을 상속받아서 operator()를 구현할 때, operator()의 매개 변수는 const T&지만 템플릿 매개 변수는 const와 참조자를 빼는것이 상례이다.
그런데 예외적으로 포인터를 매개변수로 받는 경우는 양쪽 다 const T* 타입으로 맞춰준다.
혹시라도 두 개의 기능을 합친답시고 operator()를 두개를 만드는 짓은 하지 말아야한다.
여담으로 함수자 클래스를 만들 때 클래스, 구조체 둘중 하나를 사용하는데 개인 취향의 영역으로 보인다.
논리적으로는 함수자 클래스가 상태를 가진다면 캡슐화라는 명목으로 클래스를 사용하는게 맞다.
항목 41 : ptr_fun, mem_fun, mem_fun_ref의 존재에는 분명한 이유가 있다
(참고: mem_fun은 더 이상 사용되지 않고 대신 mem_fn이 사용된다)
void test(Widget& w);
std::vector<Widget> vw;
std::for_each(vw.begin(), vw.end(), test);
비멤버 함수를 포인터나 객체로 만들어서 인자로 전달 후 호출하는것은 여태까지 많이 해왔기때문에 무리없이 구현하여 실행시킬 수 있다.
class Widget {
public:
...
void test() {}
};
std::for_each(vw.begin(), vw.end(), &Widget::test); // 될까?
그런데 만약 비멤버 함수가 아닌 멤버 함수라면 어떻게 해야될까?
위의 코드는 당연히 실행이 안된다. 인스턴스화 되지도 않은 객체의 멤버 함수를 호출하는것은 말도 안되기 때문이다.
게다가 for_each의 내부 구현은 비멤버 함수에 대해 호출하게끔 되어있다.
멤버 함수를 비멤버 함수 객체처럼 만들어주는것이 mem_fun, mem_fun_ref이다. ptr_fun과 같이 인터페이스를 바꿔주는 어댑터라고 볼 수 있다.
std::list<Widget*> lpw;
...
std::for_each(lpw.begin(), lpw.end(), std::mem_fun(&Widget::test));
mem_fun으로 만들어진 함수 객체에 의해 객체의 멤버 함수가 정상적으로 호출이 된다.
항목 42 : less<T>는 operator<의 의미임을 꼭 알아두자
class Widget {
public:
size_t weight() const;
size_t maxSpeed() const;
...
};
bool operator<(const Widget& lhs, const Widget& rhs)
{
return lhs.weight() < rhs.weight();
}
Widget 객체를 무게를 기준으로 정렬하는 operator<의 구현은 위와 같다.
그런데 만약 Widget 객체를 multiset에 담을 때, 무게가 아닌 속도를 기준으로 정렬하고 싶다면 어떻게 해야할까?
첫 번째로, less<T>를 특수화 시키는 것이다.
template<>
struct std::less<Widget> : public std::binary_function<Widget, Widget, bool> {
bool operator()(const Widget& lhs, const Widget& rhs) const {
return lhs.maxSpeed() < rhs.maxSpeed();
}
};
multiset의 기본 비교 함수는 less<T>이고, less<T>는 T객체에 정의되어 있는 operator<를 호출하게 된다.
하지만 템플릿 특수화를 통해 특정 타입에 대해서만 다르게 동작하게 제작할 수 있다.
less<Widget> 으로 Widget 객체에 한해서만 다르게 동작하도록 하면 되는 것이다.
두 번째로, 함수자 클래스를 만들어서 템플릿 매개변수로 넘겨주면 된다. less<Widget> 대신 다른걸 만들어서 넣어주면 되는 것이다.
struct MaxSpeedCompare : public std::binary_function<Widget, Widget, bool> {
bool operator()(const Widget& lhs, const Widget& rhs) const {
return lhs.maxSpeed() < rhs.maxSpeed();
}
};
std::multiset<Widget, MaxSpeedCompare> widgets;
// std::multiset<Widget> widgets; -> less<Widget> 호출
자주 봐왔던거라 딱히 특별하지도 않다.
어쨌든 less를 사용하겠다면 반드시 operator<의 의미를 가지게 해야한다는게 결론이다. 다른 기준이 필요하다면 less가 아닌 함수자 클래스를 만들어서 사용해야한다.
'도서 > Effective STL' 카테고리의 다른 글
STL 프로그래밍을 더 재미있게 해주는 팁 모음 (2) (0) | 2022.12.22 |
---|---|
STL 프로그래밍을 더 재미있게 해주는 팁 모음 (1) (0) | 2022.12.22 |
함수자, 함수 객체, 함수, 기타 등등 (1) (0) | 2022.12.20 |
알고리즘(Algorithms) (2) (0) | 2022.12.20 |
알고리즘(Algorithms) (1) (0) | 2022.12.20 |