스태틱 클래스 함수라도 자기 자신을 참조하는 스태틱 클래스 변수에 힘입어 자신의 비스태틱 멤버 변수 및 함수에 접근할 수 있게 되었다. 그러나 콜백 함수 내부에서 접근 제한이 불가능한 문제를 발견할 수 있었다. 이러한 문제는 데이터/콜백 클래스를 각기 완전히 분리를 하면 해결할 수 있다.
#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_; }; // 정적변수만을 취급하는 기본 클래스를 선언하였다. class callback_base { protected: callback_base() {} ~callback_base() {} public: static void set_class(void* _ptr) { void** base_ptr; base_ptr = get_base_ptr(); (*base_ptr) = _ptr; } protected: // static 변수를 함수 안에 삽입하였다. // 그렇지만 엄연히 cb는 존재한다. static void** get_base_ptr() { static void* base; return &base; } }; // 콜백 함수만을 정의할 수 있도록 콜백 기본 클래스를 상속받는다. class callback_class : public callback_base { // cb_base의 직접적인 생성은 금지되어 있다. protected: callback_class() {} ~callback_class() {} public: // 라이브러리가 필요로 하는 콜백 함수 // 상위 클래스에서만 스태틱 변수를 관리하고 // 상위 클래스를 상속받아 콜백 함수를 새롭게 정의한다. // storage 클래스는 사용자가 직접 만들어야 할 변수이다. static void callback() { // 멤버 변수를 조작할 수 있도록 클래스를 얻는다. storage& real = *(storage*)(*get_base_ptr()); 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; }
멤버 변수로 존재하던 정적 변수를 정적 함수 내부로 집어 넣었다. 콜백 클리스와 데이터 클래스를 분리시킴으로써 콜백이 보다 구조적인 형태를 갖추었다. 실제로 콜백을 구현할 때에는 콜백의 기본 클래스를 상속받아 라이브러리가 필요한 형태의 콜백 함수를 정적 함수 형태로 구현하면 된다.
그러나 아직 마음에 걸리는 것이 있다. 바로 콜백 부모 클래스의 ‘void *’가 그것이다. 이것이 콜백함수를 구현하는 중에 복잡한 캐스팅을 일으키게 한다. 이를 없애는 방법은 없을까? 나는 템플릿이 해결해줄 수 있을 것이라 생각한다..