항목 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가 아닌 함수자 클래스를 만들어서 사용해야한다.

+ Recent posts