Template Callback Class

복잡한 캐스팅 콜백 함수 단계에서 일어나는 것을 방지하기 위해 템플릿을 도입해 보았다.

#include <iostream>

// 전역변수 콜백 함수 포인터.
// 어떤 라이브러리 어딘가에 있다고 가정한다.
void (*g_callback)() = NULL;

// callback과 상속 관계가 없는 데이터 저장 클래스
class storage
{
public:
    storage()  {}
    ~storage() {}

    int&       val()       { return val_; }
    const int& val() const { return val_; }

private:
    int val_;
};

// 정적변수만을 취급하는 기본 클래스를 선언하였다.
template <typename T>
class callback_base
{
protected:
    callback_base() {}
    ~callback_base() {}

public:
    static void set_class(T* _ptr)
    {
        T** base_ptr;

        base_ptr    = get_base_ptr();
        (*base_ptr) = _ptr;
    }

    // 이중 포인터 표현을 감출 수 있다.
    // 어떤 것을 사용하는지는 사실 사용자의 취향에 따라 달라질 수 있다.
    inline static T& get_class() { return **get_base_ptr(); }

protected:
    // static 변수를 함수 안에 삽입하였다.
    // 그렇지만 엄연히 cb는 존재한다.
    static T** get_base_ptr()
    {
        static T* base;
        return &base;
    }
};

class callback_class : public callback_base<storage>
{
// cb_base의 직접적인 생성은 금지되어 있다.
protected:
    callback_class() {}
    ~callback_class() {}

public:
    // 라이브러리가 필요로 하는 콜백 함수
    // 상위 클래스에서만 스태틱 변수를 관리하고
    // 상위 클래스를 상속받아 콜백 함수를 새롭게 정의한다.
    // storage 클래스는 사용자가 직접 만들어야 할 변수이다.
    static void callback()
    {
        // 멤버 변수를 조작할 수 있도록 클래스를 얻는다.
        // 구질구질한 포인터 참조 연산자가 사라졌다.
        storage&  real = get_class();

        std::cout << "member variable n: " << real.val() << std::endl;
    }
};

int main(int argc, char** argv)
{
    // 일반적인 데이터 저장 클래스이다.
    // 복수 개를 선언하여 여러 콜백이 동시에 동작할 수 있는지 확인한다.
    storage s1, s2;

    s1.val() = 10;
    s2.val() = 20;

    // 같은 static 함수를 callback하되
    g_callback = &callback_class::callback;

    // 때때로 다른 데이터를 선택하도록 한다.
    // 템플릿 인자로 데이터 타입이 명시되어 가독성이 높아지는 부가적인 효과를 얻었다.
    callback_class::set_class(&s1);
    g_callback();    

    callback_class::set_class(&s2);
    g_callback(); 

    return EXIT_SUCCESS;
}

템플릿을 사용하여 클래스를 사용할 수 있는 콜백 형태를 만들었다.  하지만 여전히 몇몇 문제가 남아 있을 것이다. 그 중 “다양한 데이터 클래스들의 콜백 함수를 다룰 때는 그 다양한 인자를 어떻게 콜백 함수로 전달할 것인가?”라는 문제는 직감적으로 다가온다.

일단, 내 기본적인 아이디어는 콜백 함수에서 스태틱 포인터 변수를 활용하여 데이터 관리 객체의 비정적 멤버 변수에 접근하는 방법을 제안하는 것이며, 실제로 이 아이디어는 나 혼자만의 아이디어는 아님을 밝혀 둔다. 또한 이 방법이 최선이라고는 말할 수 없다. 더 훌륭한 방법들이 존재할 것이다.

어쨌든 C-Style의 콜백 함수가 그다지 C++에 걸맞는 구조가 아님에도 불구하고,
이런 방법을 통해 콜백 함수에서 클래스를 사용할 수 있다는 것을 제안함에 의의를 둔다.

참조:
CPUBitmap Class, J. Sanders, E. Kandrot, ‘CUDA by Example’, Addison Wesley.

차례로 돌아가기