함수 객체를 이용한 제네릭 알고리즘 구현

제네릭 알고리즘에서 함수 객체 구현

컨테이너를 다룰 때는 제너릭 알고리즘을 사용하는 것이 컨테이너가 변경되었을 때 코드의 수정의 충격을 완화시킬 수 있고, 반복되는 코드를 줄일 수 있다고 했습니다. 제네릭 알고리즘은 컨테이너의 iterator를 인자로 하는 템플릿 함수로 작성되는데 제네릭 알고리즘 내부에서 함수 객체를 구현하는 경우 한 가지 주의해야 할 점이 있습니다.
좌표를 담고 있는 Point라는 객체의 컨테이너가 있습니다. Point에는 ID라는 함수가 있어서 객체의 ID를 넘겨줍니다. 이제 Point를 담고 있는 컨테이너의 객체들을 뒤져 가장 큰 ID 값을 찾는 제네릭 알고리즘을 작성한다고 해봅시다 – 아래에서는 이 제네릭 알고리즘을 실제로 작성해 볼 겁니다.

제네릭 알고리즘에서 컨테이너의 객체들을 뒤지기 위해 max_element라는 제네릭 알고리즘을 사용합니다. max_element는 함수 객체를 인자로 전달받기 때문에 제네릭 알고리즘 내부에서 max_element에 전달하기 위한 함수 객체를 구현합니다 – 저는 함수 객체를 메소드의 첫 부분에서 구현하는 것을 선호합니다. 이렇게 하면 비슷한 용도를 하는 함수 객체를 작성하면서 이름이 충돌하는 문제로부터 자유로울 수 있고, 사실 함수 객체를 제네릭 알고리즘에 가깝게 두어 코드를 읽기 쉽게 하기 위함도 있고, 함수 객체들로 인해 전체적으로 코드가 산만해지는 것을 방지하기 위해서입니다.

주의해야 할 것은 이 함수 객체가 템플릿 클래스로 작성되었다고 하더라도 함수 객체는 제네릭 알고리즘 메소드 내부에서 구현하면 안 됩니다. 만일 제네릭 함수가 헤더 파일에서 정의되어 있어서 이를 소스 파일에서 참조하는 경우, 제네릭 알고리즘이 인스턴스화 되면서 함수 객체 템플릿 역시 인스턴스화되어 제네릭 알고리즘을 참조하고 있는 모든 소스 파일에서 동일한 이름을 갖는 클래스의 오브젝트 코드가 생성됩니다.

이 때문에 중복된 심볼 이름 문제로 링크 에러가 발생합니다. 제네릭 알고리즘을 구현하는 경우 함수 객체의 템플릿은 제네릭 알고리즘의 내부가 아닌 외부에서 정의해야 합니다.

함수 객체에는 함수 객체를 한번 더

컨테이너를 사용할 때 제네릭 알고리즘을 사용하면 반복적인 코드를 줄일 수 있습니다. 그리고,필요한 경우 함수 객체를 사용해서 STL과 제네릭 알고리즘이 가지는 설계 상의 제약을 사용할 수 있습니다 – STL은 모든 객체가 복사되도록 설계되었습니다. 그러나 컨테이너 객체의 타입에 맞춰서 함수 객체를 구현해야 하는 반복적인 코드가 문제입니다. 함수 객체에 함수 객체를 한 번 더 사용함으로써 객체의 타입에 대한 코드만 작성하면 제네릭 알고리즘을 재사용할 수 있습니다.

template 
class FindMaxIDFromPoints
{
public:
    FindMaxIDFromPoints(const Extractor& propExtractor)
        : propExtractor_(propExtractor) {
    }

    bool operator()(typename ForwardIterator::reference lhs,
                    typename ForwardIterator::reference rhs) {
        return propExtractor_(lhs) < propExtractor_(rhs) ;
    }
private:
    Extractor propExtractor_ ;
};

FindMaxIDFromPoint는 제네릭 알고리즘에서 사용할 함수 객체입니다. 함수 객체는 컨테이너의 객체의 타입이 바뀌면 동일한 역할을 하는 함수 객체라 하더라도 operator()의 구현이 매번 바뀌어야 합니다. 그래서 함수 객체에서는 객체의 특정 property를 참조하기 위해 직접 해당 메소드를 호출하지 않고 property를 추출하기 위한 함수 객체를 호출합니다. 이는 sort 등의 제네릭 알고리즘에서 비교 연산 대신 함수 객체를 호출함으로써 sort의 기능을 확장시키는 것과 동일한 아이디어입니다. 다음은 이 함수 객체를 사용하는 제네릭 알고리즘입니다.

template 
int GetNewID(ForwardIterator first,
             ForwardIterator last,
             Extractor propExtractor)
{
    ForwardIterator maxElement = max_element(
            first,
            last, 
            FindMaxIDFromPoints(propExtractor)) ;
    if ( end == maxElement ) return 0 ;
    return propExtractor(*maxElement) + 1 ;
}

이렇게 작성된 함수 객체와 제네릭 알고리즘은 다음과 같이 사용할 수 있습니다.

class IDExtractor
{
public:
    int operator()(PointObj& obj) { return obj->ID() ; }
};

PointContainer container ;
int idBase = GetNewID(container.begin(), container.end(), IDExtractor()) ;

여기서 주의할 점은 제네릭 알고리즘 내부에서 함수 객체를 구현하면 링크 에러가 발생한다는 것입니다. 그렇기 때문에 FindMaxIDFromPoints 함수 객체는 GetNewID 제네릭 알고리즘의 외부에 정의해야 합니다.

Comments are closed.

Website Built by WordPress.com.

Up ↑