一 生命周期

可以把程序看成一个生命体,也有出生、成长、结束的阶段,每个阶段都可能会有让外界来参与的需求(比如人生病了需要外界治疗,自身抵抗力不能完全自愈),如果有Windows开发经验,很容易想起WM_CREATE, WM_DESTORY等消息。或者移动端(安卓、iOS)的程序起止事件响应,甚至页面切换的事件。在每个事件中,提供了一个中断(回调函数),可以和外界进行交互。最简单的例子就是程序启动时增加一个启动画面,很多软件在退出的时候弹出一个提示窗口或打开一个浏览器链接(这种做法非常不友好,不推荐)。需要在程序的相应位置提供一个可以由外面参与的机会。这时回调函数就派上用场了。

二 回调函数一般是使用C语言的方法,不过,C++因为自身的特点,可以使用更恰当的办法(不存在好坏),我们这里使用抽象基类做参数的方法来实现,这是一种很传统的方法了,比如COM本质论这本书上第一章就讲解过。
(本文涉及到虚函数的一些知识,还有抽象基类,一般C++教材会讲到。)

本文在前文的代码上面增加回调机制。

以下先讲关键代码。

点击(此处)折叠或打开

  1. #include <iostream>// stl cout
  2. #include <signal.h>//signal头文件
  3. #include <chrono> //stl chrono头文件,时间工具,可以精确到,可以精确到纳秒
  4. #include <thread> //stl thread

  5. #include "include/glog/logging.h"
  6. #pragma comment(lib, "lib/Release/glog.lib")
  7. using namespace google;

  8. class IAppCB
  9. {
  10. public:
  11.     virtual int onInit() = 0;
  12.     virtual int onDestroy() = 0;
  13. };

  14. class AppCBImpl :public IAppCB
  15. {
  16. public:
  17.     virtual int onInit()
  18.     {
  19.         std::cout << "onInit\n";
  20.         
  21.         return 0;
  22.     };
  23.     virtual int onDestroy()
  24.     {
  25.         std::cout << "onDestroy\n";
  26.         return 0;
  27.     };
  28. };


  29. static int signaled = 0;
  30. static void sigterm_handler(int sig)
  31. {
  32.     signaled = 1;
  33. }

  34. class App
  35. {
  36. public:
  37.     void run()
  38.     {
  39.         //std::chrono::milliseconds可以是以下四个中的一个:seconds,milliseconds,microseconds,nanoseconds
  40.         uint64_t start_millseconds = std::chrono::duration_cast<std::chrono::milliseconds>
  41.             (std::chrono::system_clock::now().time_since_epoch()).count();
  42.         for (;;)
  43.         {
  44.             if (signaled == 1)
  45.                 break;
  46.             else
  47.             {
  48.                 std::cout << "运行毫秒数:" << std::chrono::duration_cast<std::chrono::milliseconds>
  49.                     (std::chrono::system_clock::now().time_since_epoch()).count() - start_millseconds << std::endl;

  50.                 //c++11支持u8转utf8,否则写到文件里乱码
  51.                 LOG(INFO) << u8"运行毫秒数:" << std::chrono::duration_cast<std::chrono::milliseconds>
  52.                     (std::chrono::system_clock::now().time_since_epoch()).count() - start_millseconds ;
  53.                 std::this_thread::sleep_for(std::chrono::seconds(1));
  54.             }
  55.         }
  56.     };
  57. };

  58. IAppCB* m_pCB = nullptr;
  59. int main()
  60. {
  61.     m_pCB = new AppCBImpl;
  62.     google::InitGoogleLogging("demo3.exe");//
  63.     google::SetLogDestination(google::GLOG_INFO, "demo3_");
  64.     LOG(INFO) << "Hello World!";
  65.     std::cout << "Hello World!\n";
  66.     if (m_pCB->onInit() != 0)//程序启动时由外部确定需要执行哪些操作,比如显示欢迎页。如果程序初始化失败,onInit 非0数值,程序退出。
  67.     {
  68.         delete m_pCB;
  69.         m_pCB = nullptr;
  70.         return 0;
  71.     }
  72.     signal(SIGINT, sigterm_handler); //ctrl + c中断


  73.     App app;
  74.     app.run();
  75.     std::cout << "Exit!\n";
  76.     LOG(INFO) << "Exit!";
  77.     m_pCB->onDestroy();//程序退出前由外部决定还要执行哪些操作,比如各种流氓软件退出时弹出页面。

  78.     std::this_thread::sleep_for(std::chrono::seconds(1));
  79.     google::ShutdownGoogleLogging();
  80.     
  81.     delete m_pCB;
  82.     m_pCB = nullptr;
  83.     return 0;
  84. }

这个代码主要是在前文的基础上增加了两个class,一个是抽象基类:IAppCB,这个名字的意思是 App的回调函数接口。一般class名字第一个字母用I 表示是接口(Intereface),写过COM的朋友很清楚。最后两个字母CB表示回调(callback),使用过微软SDK的朋友也有感觉,比如DirectShow里面大量的CB类。

AppCBImpl继承IAppCB,名字带Impl,如果了解设计模式会知道这是其中桥接模式(Bridge),写Java代码中会经常使用。
这两个类,IAppCB声明接口,AppCBImpl负责实现,标准的面象对象设计方法。因为C++首先是兼容C,所以一般初学都没有这方面意识,大多还是面向过程的方式来写代码。而Java是重新设计的,最初就采用了这种模式,可能好多人还不了解什么是设计模式,就照葫芦画瓢模仿着写,所以感觉Java学起来比C++容易。其实只要理解了里面的知识点,用哪种语言写起来都一样。

继续来,IAppCB* m_pCB = nullptr;
然后再代码里m_pCB = new AppCBImpl;
这样写还是面向过程,不是完全的面向对象。不过还不急,这部分还相当于伪代码,演示作用,后续会慢慢纠正。

本文重点是增加了两处:
第一处:if (m_pCB->onInit() != 0)
代码的注释里也写了,这相当于一般程序的启动时,通知外界,是否需要干涉。一般可以在这个函数里做一些初始化的动作。
第二处:
m_pCB->onDestroy();
这是程序退出时要执行的操作。比如一般程序在退出时保存当前执行状态,关键的参数等。

回调函数除了程序本身用以外,还可以对外开放。比如程序本身封成SDK,源码不可能,但提供 还 IAppCB的接口,调用着可以自己定义一个子类执行具体参数,然后把接口声音的变量传到主程序中,可以自己控制程序的执行。

本文完整代码在https://github.com/sxcong/cpp_project_in_action/blob/master/demo3/test3.cpp
与文中的一致。



09-02 03:13