이글의 한글판은 다음 링크를 따라 가시면 됩니다.

C++이야기 스물아홉번째: Portable C++ Timer Class #2


This time, let's dig into what should go into the Timer-related classes. I think we should start with TimerDriver because it is a class which users will play with in most cases.

As I mentioned before, the TimerDriver is a class which will expire registered timer instances at the designated time. Considering this, it is apparently necessary to define member functions for registering and unregistering timer instances as follows.

class TimerDriver {
public:
    TimerId RegisterTimer(const Timer& t);
    void UnregisterTimer(TimerId tid);
};

After defining this interface, we quickly come to the idea that we need some storage for registered timer instances. Then which data structure should we use to store them? We need to think how for users to use the TimerDriver to pick up the most appropriate data structure.

The only member functions which users can use upto now are RegisterTimer() and UnregisterTimer(), which are similar to insertion and deletion. Users will drive the registered timer instances by traversing timer instances. And I'm quite sure that users do need neither sorted timer list, nor direct access to a timer instance, nor searching a timer instance very fast.

So, I think that the most appropriate data structure for the registered timer instances is std::list.

#include <list>

class Timer;

class TimerDriver {
public:
    TimerId RegisterTimer(const Timer& t);
    void UnregisterTimer(TimerId tid);

private:
    std::list<Timer> timer_list_;
};

Next, let's think about RegisterTimer() and UnregisterTimer()'s parameters and return values.

I introduced 'TimerId' type as a return value of RegisterTimer() and input parameter of UnregisterTimer(). The necessity of 'TimerId' type is very clear but what would the TimerId be like? Should the TimerDriver manage a identifier pool in it or could the TimerId be Timer*? If the TimerDriver manage a identifer pool, then where should each assigned identifer to a timer instance be stored? Is it OK that timer_list_ be just list<Timer> or should be something else, say, list<Timer*>? All these questions will hang around our mind until we come to sort of a conclusion. Totally, like a mess, huh? even for this simple class?

But still, as a software engineer, we need to handle this mess, finding rationales for our choices. Our life is full of problems and living itself is a journey to find a solution, right? So, don't be panic and calm down. Actually, all the questions are related to each other. And when we find a clue to one question, then Boom! we can find answers suddenly for all questions.

Let's solve problems one by one. Take a deeeeeeeeeeeeeeep breath.

TimerId! Should the TimerDriver manage identifer pool? Probably. The TimerDriver can assign a free id from identifier pool to newly registered timer instance whenever a user ask it to do by calling RegisterTimer(). And it should store the assigned identifier somewhere so that it can delete the timer when UnregisterTimer() is requested. But I really hesitate to do it now because that solution seems to be an overkill, considering current requirements. I DO prefer simpler design, which leads us to lesser codes and lesser bugs. So I exclude identifier pool solution from candidate designs. Then what are other solutions there? What do you think about the idea that we can just define the TimerId as Timer*

#include <list>

class Timer;
typedef Timer* TimerId;

class TimerDriver {
public:
    TimerId RegisterTimer(const Timer& t);
    void UnregisterTimer(TimerId tid);

private:
    std::list<Timer> timer_list_;
};

With this approach, we don't need to define some sort of identifier pool and RegisterTimer() implementation will be very simple because it can just return &t as a return value. right? Then we found nearly perfect solution. right? Uh! Uh! Uh! The life is not that easy. Let's think it more carefully.

To simply define the TimerId as Timer* is to open a gate for user to handle directly internal representation of timer instances, which breaks encapsulation rule. If we change the internal representation of timer instance inside TimerDriver, then that will break some user codes which are dependent on the fact that TimerId is Timer*. So I'd rather to define TimerId as void*. Then users will have no idea about what the internal representation of Timer inside TimerDriver.

#include <list>

class Timer;
typedef void* TimerId;

class TimerDriver {
public:
    TimerId RegisterTimer(const Timer& t);
    void UnregisterTimer(TimerId tid);

private:
    std::list<Timer> timer_list_;
};

Next, let's think about timer_list_ member variable. As I mentioned in the previous article, I'd like to define PeriodicTimer and OneShotTimer which are derived from Timer. We should define common interface in Timer and two timer classes will implement that interface so that they show polymorphic behaviors. Then, we need to define timer_list_ as std::list<Timer*> instead.

#include <list>

class Timer;
typedef void* TimerId;

class TimerDriver {
public:
    TimerId RegisterTimer(const Timer& t);
    void UnregisterTimer(TimerId tid);

private:
    std::list<Timer*> timer_list_;
};

I think there is one missing member function in current TimerDriver's interface. That is, sort of a driving function. Typically users first register timer instances and then run those instances. So I add Run() member function as follows

#include <list>

class Timer;
typedef void* TimerId;

class TimerDriver {
public:
    TimerId RegisterTimer(const Timer& t);
    void UnregisterTimer(TimerId tid);
    void Run();

private:
    std::list<Timer*> timer_list_;
};

Now, I feel that the interface of TimerDriver is nearly perfect. Let's think about the implementation of TimerDriver in the next article.


이글의 한글판은 다음 링크를 따라 가시면 됩니다.

C++이야기 스물아홉번째: Portable C++ Timer Class #1


You know the time is all around us. Similarly the time is all around program from applications to firmwares. For example, a calendar application deals with time-related appointments and/or tasks, a search bot fetches pages periodically,  a multi-media player pumps up media data from data source in time-sensitive way, a communication middleware over TCP/IP deals with timeouts for data exchange and/or operations, and a device driver sends a status check request periodically to a device. The time is so fundamental that sooner or later we need a library that deals with timeouts and periodical events except only that we develop a very small program.


This time, I'd like to talk about how to implement timer class, which supports timeouts and periodical events, using boost library.


Let's think what fundamental concepts are related to a timer.

  1. The timing event may be periodical or expired only once.
  2. A particular job should be done at the timing event.
  3. There is a driver which expires the timing event at the exact time.


For now, I can list up the above concepts.


First, let's start with how to define class(es) from the concept 1? We can define one class which supports both types through some enum values as follows.


class Timer {

public:

  typedef enum {

    PERIODIC_TIMER,

    ONE_SHOT_TIMER

  } Type;


private:

  Type m_type;

};


Is this design decision is good, that two separate concepts are combined into one class? Let's think it more carefully. What should the constructor be like?


#include <boost/date_time.hpp> // for time_duration


class Timer {

public:

  typedef enum {

    PERIODIC_TIMER,

    ONE_SHOT_TIMER

  } Type;


  Timer(Type type, const boost::posix_time::time_duration& td);


private:

  Type m_type;

};


What 'type' argument means is clear but what does 'td' means? It depends on 'type', right? Because typically periodic events require period(== 'td') but timeout events require time which it expires at(== current time + 'td'). So, implementation of Timer::Timer() will also depend on 'type' as follows


Timer::Timer(Type type, const boost::posix_time::time_duration& td)

{

  switch (type)

  {

  case PERIODIC_TIMER:

    ......

    break;


  case ONE_SHOT_TIMER:

    ......

    break;


  default:

    break;

  }

}


We can easily guess that whenever we add another type of timer, we should change this constructor. This will be a serious maintenance problem, huh? There are another problems. From concept 3, we need to define a driver class. For example, TimerDriver


class TimerDriver { ...... };


The TimerDriver should know what the timer's type is to drive different timer instances properly. So the TimerDriver's driving member function will also depend on Timer's type. This thought leads us to a problem that whenever we add another type of timer, we should change the TimerDriver's driving member function.


The last problem that comes to my mind is that the periodic events need to store period but timeout events time which it will expire at. So, there will be wasteful storage in Timer class if we define two separate member variables in one class.


Apparently it is a better design to me to define separate classes for periodic events and timeout events. But still the TimerDriver need to deal with those classes uniformly. So, I'd like to introduce a common base class Timer, and derived classes PeriodicTimer and OneShotTimer.


class Timer { ...... };


class PeriodicTimer : public Timer { ...... };

class OneShotTimer : public Timer { ...... };


Then what should go into either Timer, PeriodicTimer or OneShotTimer? Let's dig into this issue in the next article. ^^