Timer class 검토 좀 해주세요~

Posted at 2008.11.18 06:20 // in S/W개발/C++ 이야기 // by 김윤수



Timer class 를 아래와 같이 구현하려고 하는데... 괜찮은 설계일까요?

struct Timer
{
    typedef boost::function<void ()> CallbackObjectType;

    Timer(int period, CallbackObjectType cbo) :
        m_period(period), m_cbo(cbo)
    {}

    int m_period;
    CallbackObjectType m_cbo;
};

inline Timer
MakeTimer(int period, void (*func)())
{
    Timer::CallbackObjectType cbo = func;
    return Timer(period, cbo);
}

template <typename Callee>
struct CallbackMemFunc0 {
    typedef void (Callee::*Type)();
};

template <typename Callee>
inline Timer
MakeTimer(int period, Callee* callee, const typename CallbackMemFunc0<Callee>::Type& memFunc)
{
    return Timer(period, boost::bind(memFunc, callee));
}

template <typename ArgType>
struct CallbackFunc1 {
    typedef void (*Type)(ArgType);
};

template <typename ArgType>
inline Timer
MakeTimer(int period, const typename CallbackFunc1<ArgType>::Type& func, ArgType arg)
{
    return Timer(period, boost::bind(func, arg));
}

template <typename Callee, typename ArgType>
struct CallbackMemFunc1 {
    typedef void (Callee::*Type)(ArgType);
};

template <typename Callee, typename ArgType>
inline Timer
MakeTimer(int period, Callee* callee,
          const typename CallbackMemFunc1<Callee, ArgType>::Type& memFunc,
          ArgType arg)
{
    return Timer(period, boost::bind(memFunc, callee, arg));
}

굳이 Timer class는 encapsulation 시킬 필요가 없다고 생각해서 그냥 struct로 선언해 버렸구요. MakeTimer를 여러개 만들어 놓았는데... 아무리 봐도 맘에 안드네요. 결국은 계속해서 반복되는 코드 패턴이라서... 게다가 컴파일러가 template <typename Callee> MakeTimer(...) 와  template <typename ArgType> MakeTimer(...)를 잘 구분못해서 사용자가 직접 template 인자를 알려줘야 해서 불편하기도 하고...

    // Timer t(100, bind(&Base::print, new Base()));
    Timer t1 = MakeTimer(100, new Base(), &Base::print);
    t1.m_cbo();

    Timer t2 = MakeTimer<const string>(100, sayHelloTo, string("YoonSoo Kim"));
    t2.m_cbo();
   
    Timer t3 = MakeTimer(100, new Hello(), &Hello::sayHelloTo, string("YoonSoo Kim"));
    t3.m_cbo();

이렇게 반복되는 코드 없이 깔끔하게 설계할 수 있는 방법은 없을까요????

신고
  1. 김윤수

    2008.11.23 18:48 신고 [수정/삭제] [답글]

    period 도 int 로 하지 말고 boost::posix_time::time_duration 을 쓸까 합니다. 그리고, boost::bind()를 되도록이면 안써도 되게 하려고 했는데, 그렇게 될 경우 거의 boost::bind() 에 필적하는 MakeTimer()를 만들어야 할 것 같아서 현재 시점에서는 일단은 포기하고, 그냥 기본 constructor 만 제공하려고 합니다. Timer 를 만드는 사람이boost::bind() 를 직접 써서 functor 를 만들어서 넘겨주는 것으로 하구요.

  2. 비블레리

    2010.09.29 22:23 신고 [수정/삭제] [답글]

    우선 님의 블로그 잘 읽고 있습니다.
    C++에 대한 개념들을 이해하기 쉽게 잘 정리해 놓으셨네요.
    앞으로도 좋은 내용들을 기대하겠습니다^^.

    님이 설계하신 Timer 클래스는 임의의 콜백함수
    (일반 함수일 수도 있고 클래스의 멤버함수일 수도 있고, 또 매개변수가 0부터 n인 함수들)
    을 설정했다가 원하는 시점에 호출을 하도록 하는 것이 목적인 것 같습니다.

    저도 좀 고민을 해 봤는데요,
    님이 고민하시는 것처럼 콜백함수의 매개변수에 따른 반복적인 코딩은 어쩔 수 없는 것 같습니다.
    하지만 MakeTimer() 함수와 이 함수에 필요한 콜백함수 타입정의 구조체(CallbackMemFunc0, CallbackMemFunc1)의
    분리에서 오는 복잡성은 줄일 수 있을 것 같습니다.


    제가 구현한 클래스(CTimer)는 다음과 같습니다.
    여기서는 콜백함수의 매개변수 개수를 최대 2개로 제한했습니다.
    그리고 boost 라이브러리 대신 tr1 라이브러리를 사용했습니다.


    // 우선, primary template class를 정의합니다.

    template<typename Callee, typename Arg1, typename Arg2>
    class CTimer;


    // 그 다음에 각각의 경우에 대한 specialized template class들을 정의합니다.

    // 일반 함수를 위한 클래스들.

    template<>
    class CTimer<void, void, void>
    {
    typedef std::tr1::function<void ()> FUNC;
    public:
    CTimer(FUNC func)
    : m_func(func)
    {}
    ~CTimer() {}

    void Call() { m_func(); }

    private:
    FUNC m_func;
    };

    template<typename Arg1>
    class CTimer<void, Arg1, void>
    {
    typedef std::tr1::function<void (Arg1)> FUNC;
    public:
    CTimer(FUNC func, Arg1 arg1)
    : m_func(func), m_arg1(arg1)
    {}
    ~CTimer() {}

    void Call() { m_func(m_arg1); }

    private:
    FUNC m_func;
    Arg1 m_arg1;
    };

    template<typename Arg1, typename Arg2>
    class CTimer<void, Arg1, Arg2>
    {
    typedef std::tr1::function<void (Arg1, Arg2)> FUNC;
    public:
    CTimer(FUNC func, Arg1 arg1, Arg2 arg2)
    : m_func(func), m_arg1(arg1), m_arg2(arg2)
    {}
    ~CTimer() {}

    void Call() { m_func(m_arg1, m_arg2); }

    private:
    FUNC m_func;
    Arg1 m_arg1;
    Arg2 m_arg2;
    };

    // 클래스 멤버함수를 위한 클래스들.
    template<typename Callee>
    class CTimer<Callee, void, void>
    {
    typedef std::tr1::function<void (Callee*)> FUNC;
    public:
    CTimer(FUNC func, Callee* pCallee)
    : m_func(func), m_pCallee(pCallee)
    {}
    ~CTimer() {}

    void Call() { std::tr1::bind(m_func, m_pCallee)(); }

    private:
    FUNC m_func;
    Callee* m_pCallee;
    };

    template<typename Callee, typename Arg1>
    class CTimer<Callee, Arg1, void>
    {
    typedef std::tr1::function<void (Callee*, Arg1)> FUNC;
    public:
    CTimer(FUNC func, Callee* pCallee, Arg1 arg1)
    : m_func(func), m_pCallee(pCallee), m_arg1(arg1)
    {}
    ~CTimer() {}

    void Call() { std::tr1::bind(m_func, m_pCallee, m_arg1)(); }

    private:
    FUNC m_func;
    Callee* m_pCallee;
    Arg1 m_arg1;
    };

    template<typename Callee, typename Arg1, typename Arg2>
    class CTimer
    {
    typedef std::tr1::function<void (Callee*, Arg1, Arg2)> FUNC;
    public:
    CTimer(FUNC func, Callee* pCallee, Arg1 arg1, Arg2 arg2)
    : m_func(func), m_pCallee(pCallee), m_arg1(arg1), m_arg2(arg2)
    {}
    ~CTimer() {}

    void Call() { std::tr1::bind(m_func, m_pCallee, m_arg1, m_arg2)(); }

    private:
    FUNC m_func;
    Callee* m_pCallee;
    Arg1 m_arg1;
    Arg2 m_arg2;
    };


    이렇게 정의해 놓으면 다음과 같이 사용할 수 있습니다.



    void print()
    {
    std::cout << "print() called" << std::endl;
    }

    void print1(int i)
    {
    std::cout << "print1(" << i << ") called" << std::endl;
    }

    void print2(int i, int j)
    {
    std::cout << "print2(" << i << ", " << j << ") called" << std::endl;
    }

    class CPrint
    {
    public:
    void Print()
    {
    std::cout << "CPrint::Print() called" << std::endl;
    }

    void Print1(int i)
    {
    std::cout << "CPrint::Print1(" << i << ") called" << std::endl;
    }

    void Print2(int i, int j)
    {
    std::cout << "CPrint::Print2(" << i << ", " << j << ") called" << std::endl;
    }
    };


    int _tmain(int argc, _TCHAR* argv[])
    {
    CTimer<void, void, void> myTimer1(&print);
    myTimer1.Call();

    CTimer<void, int, void> myTimer2(&print1, 1);
    myTimer2.Call();

    CTimer<void, int, int> myTimer3(&print2, 1, 1);
    myTimer3.Call();


    CPrint p;

    CTimer<CPrint, void, void> myTimer4(&CPrint::Print, &p);
    myTimer4.Call();

    CTimer<CPrint, int, void> myTimer5(&CPrint::Print1, &p, 1);
    myTimer5.Call();

    CTimer<CPrint, int, int> myTimer6(&CPrint::Print2, &p, 1, 1);
    myTimer6.Call();


    return 0;
    }


    매개변수의 개수가 늘어날 때마다 그에 맞는 specialization template class를 정의해 주어야 한다는 것과,
    CTimer 개체를 선언할 때마다 정확한 template parameter들을 써 주어야 한다는 것이 신경쓰이는 부분이네요.

댓글을 남겨주세요.

티스토리 툴바