@guochy2012
2014-08-10T04:12:01.000000Z
字数 4374
阅读 3111
首次接触到基于对象是在半年前,上网搜了一下,发现网上的资料基本都是复制粘贴,基于对象没有继承和多态等,基本没有谈到问题的核心。
下面我讲结合一个定时器类的封装谈谈什么是基于对象,以及用它取代面向对象的好处。
首先我用C编写一个简单的定时器,该定时器使用timerfd系列的函数,定时器的触发不再依靠信号,而是fd可读,所以可以把它交给poll去触发。
这里是源代码:
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <sys/timerfd.h>#include <poll.h>#define ERR_EXIT(m) \do { \perror(m);\exit(EXIT_FAILURE);\}while(0)int main(int argc, const char *argv[]){//创建定时器的fdint timerfd = timerfd_create(CLOCK_REALTIME, 0);if(timerfd == -1)ERR_EXIT("timerfd_create");//开启定时器,并设置时间struct itimerspec howlong;memset(&howlong, 0, sizeof howlong);howlong.it_value.tv_sec = 5; //初始时间howlong.it_interval.tv_sec = 1; //间隔时间if(timerfd_settime(timerfd, 0, &howlong, NULL) == -1)ERR_EXIT("timerfd_settime");struct pollfd event[1];event[0].fd = timerfd;event[0].events = POLLIN;char buf[1024];while(1){int ret = poll(event, 1, 10000);if(ret == -1)ERR_EXIT("poll");else if(ret == 0)printf("timeout\n");else{//这里必须read,否则poll被不停的触发if(read(timerfd, buf, sizeof buf) == -1)ERR_EXIT("read");printf("foobar .....\n");}}close(timerfd);return 0;}
(这里稍微抱怨下,当初学习tiemrfd系列定时器时,这个模型非常简单,但是中文社区几乎没找到文章能把它阐释清楚,很多文章只是简单介绍几个函数,就把man手册中的代码粘上作为demo)
这个定时器的逻辑非常简单,我们设置的间隔时间为1s,所以每隔1s,对应的timerfd就变为可读,此时poll返回,执行第44行,用户的操作。
观察上面的代码,可以发现一个事实,除了极少数代码,如定时器的初始时间/间隔时间和用户自定义的逻辑外,其他的地方基本都是固定的。于是我们用class对其进行封装。
定时器的时间可以做成单独的函数,去设置数据成员,定时器的操作呢?我们自然想到采用虚函数,而且是纯虚函数。
于是我写出以下的class:
class Timer{public:// ...void setTimer(int val, int interval); //设置时间virtual void timeout() = 0; //超时执行的操作// 以下简略};
这个类的编写没有问题,那么如何使用呢? 答案很简单,稍微学过面向对象的读者就知道,我们需要写一个类继承Timer,然后实现其中的timeout纯虚函数即可。
如下:
class MyTimer : public Timer{public:void timeout(){printf("foobar......\n");}// 其他省略};
这种把模型封装成基类,然后用户去继承,去实现纯虚函数的做法,叫做面向对象的解决方案。
基于对象,我们采用的是一对神器function/bind,这原本是Boost库的组件,于2005年置入C++的TR1,现已经成为C++11的内置标准。
本文不是function/bind的初级教程,这里假定读者已经掌握了其基本使用。
之前的Timer,可以这样封装:
class Timer{public:typedef std::function<void ()> TimerCallback;// ...void setTimer(int val, int interval); //设置时间void setTimerCallback(const TimerCallback &cb); //设置回调函数// ...private:TimerCallback timerCallback_; //回调函数};
现在的使用就非常简单了,用户只需要自己编写一个回调函数,然后注册给定时器即可。
void foobar() //这是回调函数{printf("foobar.....\n");}int main(){Tiemr t;t.setTimer(4, 1);t.setTimerCallback(&foobar); //注册foobar为回调函数t.startTimer();return 0;}
如果你操作的数据比较复杂,当然可以封装成类,但是此时用的是组合,而不是继承。
class MyTimer{public:// ....void foobar(){printf("foobar...\n");}void setCallback(){timer_.setTimerCallback(std::bind(&MyTimer::foobar, this)); //这里展示了bind的用法}//其他接口,例如启动定时器等private:Timer timer_;//Other Data}
可以看看这个实现,Timer和Thread的实现参见:Timer、Thread
class FooThread{public:FooThread();void print();void startTimerThread();private:Thread thread_;Timer timer_;};FooThread::FooThread(){}void FooThread::print(){cout << "Hello World" << endl;}void FooThread::startTimerThread(){timer_.setTimer(3, 1);timer_.setTimeCallback(bind(&FooThread::print, this));thread_.setCallback(bind(&Timer::runTimer, &timer_));thread_.start();thread_.join();}int main(int argc, const char *argv[]){FooThread foo;foo.startTimerThread();return 0;}
我们先提出一个问题:我们在main里面执行了定时器,那么main就无法从事其他工作了,毕竟只有一个线程。
于是我们打算将Timer封装在一个线程中,称为TimerThread,TimerThread启动后便新建线程执行定时器,而不占用当前线程。
如果是采用面向对象,那么Thread也是采用由用户去实现纯虚函数run的方法实现的。
例如:
class Thread{public:virtual void run() = 0; // 用户继承去实现这个函数};
现在我们去编写TimerThread类,如何具体实现呢?我们要实现Thread里面的run函数,还要保留Timer的功能,更重要的是,线程启动后需要启动定时器,所以二者之间是一种调用关系。
这里我决定采用多重继承。
问题又来了,Thread、Timer我们都采用public继承?我们知道public继承是一个"is-a"的关系,TimerThread我更倾向于看做是一个Thread类,而对于Timer类,我打算采用实现继承而不是接口继承。
于是,我们采用public继承Thread,private继承Timer。如下:
class TimerThread :public Threadprivate Timer{public:void run() //线程的run方法{Timer::startTimer(); //在线程里面开启定时器}private:// ....};
这个类依然是抽象类,因为Timer的纯虚函数还没有实现,用户去继承它,实现定时器的timeout函数,就可以使用了。
采用基于对象的解决方案会简单很多,我们把Thread和Timer组合起来即可。
这里注意,我们让用户自定义回调函数,传给Timer,然后把Timer的startTimer作为回调函数,传给Thread。
class TimerThread : private NonCopyable{public:typedef std::function<void()> TimerCallback;// Constructvoid setTimerCallback(const TimerCallback &cb);// Otherprivate:Timer timer_;Thread thread_;};
我们看一下里面回调函数的绑定顺序:
void TimerThread::setTimerCallback(const TimerCallback &cb){timer_.setTimerCallback(cb); //设置Timer的回调thread_.setCallback(bind(&Timer::runTimer, &timer_)); //设置线程的回调}
这里有一份完整的基于对象实现代码:TimerThread
这就是基于对象的解决方案。
首先第一点,面向对象是基于继承和多态的,这里的继承是public继承。而基于对象主要使用了function/bind实现事件的回调。
其次,二者的本质区别在于基于对象把OOP中的虚函数改为了由function实现的回调函数。
第三,OOP采用虚函数,所以用户必须去继承并实现它,而基于对象则不然,用户写全局函数/类的成员函数皆可,更甚者,我们有bind,可以自由的修改函数的签名信息,主要是参数列表。
最后,我们借助function/bind,实现了C#中的委托(delegate)机制。