POCO C++库学习和分析 -- 序

POCO C++库学习和分析 -- 序


1. POCO库概述:

POCO是一个C++的开源库集。同一般的C++库相比,POCO的特点是提供了整一个应用框架。如果要做C++程序应用框架的快速开发,我觉得STL+boost+Poco+Qt+Mysql实在是个不错的组合。

下面的这张图提供了POCO库的一个结构。


对于POCO概述更加详细的介绍,可以看一下官方网站和《POCO C++库导游》以及《POCO C++简介》这篇文章。

 

对于我来说,POCO C++的可学习之处至少有以下几点:

1.      跨平台库的封装

2.      Application的应用架构的模块化。

3.      不同操作系统的底层API使用

4.      类的设计和设计模式的应用

5.      泛型


接下来的一系列文章就是我在学习时的一些体会。(本文对应的Poco库代码版本为poco-1.4.2p1).


2. Foundation 库分析:

          1. POCO C++库学习和分析 --  跨平台库的生成

          2. POCO C++库学习和分析 --  Foundation库结构

          3. POCO C++库学习和分析 -- Foundation库SharedLibrary模块分析

          4. POCO C++库学习和分析 --  线程 (一)

          5. POCO C++库学习和分析 -- 线程 (二)

          6. POCO C++库学习和分析 -- 线程 (三)

          7. POCO C++库学习和分析 -- 线程 (四)

          8. POCO C++库学习和分析 -- 任务

          9. POCO C++库学习和分析 -- 内存管理 (一)

          10. POCO C++库学习和分析 -- 内存管理 (二)

          11. POCO C++库学习和分析 -- 内存管理 (三)

          12. POCO C++库学习和分析 -- 进程

          13. POCO C++库学习和分析 -- 通知和事件 (一)

          14. POCO C++库学习和分析 -- 通知和事件 (二)

          15. POCO C++库学习和分析 -- 通知和事件 (三)

          16. POCO C++库学习和分析 -- 通知和事件 (四)

          17. POCO C++库学习和分析 -- 数据类型转换

          18. POCO C++库学习和分析 -- 哈希

          19. POCO C++库学习和分析 -- Cache

          20. POCO C++库学习和分析 -- 字符编码

          21. POCO C++库学习和分析 -- 平台与环境

          22. POCO C++库学习和分析 -- 日期与时间

          23. POCO C++库学习和分析 -- 异常、错误处理、调试

          24. POCO C++库学习和分析 --  随机数和数字摘要

          25. POCO C++库学习和分析 -- 文件系统

          26. POCO C++库学习和分析 -- 日志 (一)

          27. POCO C++库学习和分析 -- 日志 (二)

          28. POCO C++库学习和分析 -- 流 (一)

          29. POCO C++库学习和分析 -- 流 (二)

          30. POCO C++库学习和分析 -- 流 (三)

          31. POCO C++库学习和分析 -- URI

          32. POCO C++库学习和分析 -- UUID



3. 附录:

POCO c++library:http://pocoproject.org/

POCO 的文档: http://poco.sourcearchive.com/

:http://hi.baidu.com/marsjin/item/1b0d86bb9f2e61f162388e30

: http://blog.sina.com.cn/s/blog_68ce7fc30100v3mt.html


(版权所有,转载时请注明作者和出处 http://blog.csdn.net/arau_sh/article/details/8568654)



 

POCO C++库学习和分析 --  Foundation库结构

Foundation库是POCO库集中的一个,提供了编程时的一些常用抽象。在程序中被分成了18个部分,分别是:

1)Core

这部分除了建立跨平台库的基础头文件外,最有意义的部分是分装了原子计数的基本类(AtomicCounter),以及垃圾收集的一些类,如AutoPtrSharedPtr

2)Cache

顾名思义,内存Cache

3)Crypt

数字摘要

4)DateTime

时间

5)Events

分装了事件

6)Filesystem

文件系统,主要是对文件本身的操作,如移动,拷贝文件等

7)Hashing

Hash表

8)Logging

日志系统

9)Notifications

通知

10)Processes

进程通讯

11)RegularExpression

正则表达式,依赖于PCRE库.(http://www.pcre.org)

12)SharedLibrary

文件和类的动态实时加载

13)Streams

14)Tasks

任务

15)Text

文本装换

16)Threading

多线程

17)URI

URI操作

18)UUID

UUID生成和操作

 

在这18个模块中,Core、Events、Notifications、Processes、Tasks、Threading这几个模块应用时,对于创建整体程序架构的影响非常大,基本上可以决定了一个应用程序的复杂度,合理的应用这些模块可以使应用程序松耦合。其余的一些模块对应用整体结构影响不大,带来的都是一些局部的影响。

在看POCO库的时候经常觉得它的类写得好,内聚性非常强,耦合性很低。这个和它整体结构的合理性确实也是有一定关系的。

 

POCO C++库学习和分析 -- Foundation库SharedLibrary模块分析

 

对于一个不熟悉的开源库和模块,我觉的最好的学习方法莫过于:

        1. 使用库,看库实现了什么功能和接口;

        2. 抛开库,想一想,自己如何实现。可以想出的出来是最好的,想不出其实也没什么关系,至少有了疑问。

        3. 看库的内层代码,学习和比较作者思路。

 

SharedLibrary的功能一句话可以概括,在运行时动态的加载库和库内的类。也就是说SharedLibrary提供了一个架构或者是约定,供库使用方和库提供方使用。只要满足了模块约定,就可以快速实现调用。

        对于库的调用来说,导出函数和导出类是基本的功能,windows和linux下具是如此,因此SharedLibrary也必须实现此功能。

 

1.1 导出函数

        先来看一个例子,说明导出函数是如何使用的。

        对于库提供方而言:

 

              //TestLibrary.cpp

#include

#if defined(_WIN32)

#define LIBRARY_API__declspec(dllexport)

#else

#define LIBRARY_API

#endif

extern "C" voidLIBRARY_API hello();

void hello()

{

       std::cout << "Hello,world!" << std::endl;

}

 

// 对于使用方而言:

              //LibraryLoaderTest.cpp

#include"Poco/SharedLibrary.h"

using Poco::SharedLibrary;

typedef void (*HelloFunc)();// function pointer type

int main(int argc, char** argv)

{

        std::stringpath("TestLibrary");

        path.append(SharedLibrary::suffix());// adds ".dll" or ".so"

        SharedLibrary library(path); // willalso load the library

        HelloFunc func = (HelloFunc)library.getSymbol("hello");

        func();

        library.unload();

        return 0;

}

上述步骤,和调用普通的window dll和linux so文件步骤是如此的类似:第一步加载库文件,第二步获取库中API的函数地址,第三步运行函数。不同是所有的功能从操作系统提供的API变成了封装类SharedLibrary的类操作。

 

1.2  导出类

        再来看一个例子,说明SharedLibrary模块中类是如何导出并被使用的。
        对于库提供方:


.h文件

// AbstractPlugin.h

//

// This is used both by theclass library and by the application.

#ifndefAbstractPlugin_INCLUDED

#defineAbstractPlugin_INCLUDED

class AbstractPlugin

{

public:

                     AbstractPlugin();

                     virtual ~AbstractPlugin();

                     virtual std::string name() const = 0;

};

#endif // AbstractPlugin.h

 

.cpp文件

         //AbstractPlugin.cpp

//

// This is used both by the class library and bythe application.

#include "AbstractPlugin.h"

AbstractPlugin::AbstractPlugin()

{}

AbstractPlugin::~AbstractPlugin()

{}

 

 

// PluginLibrary.cpp

#include "AbstractPlugin.h"

#include "Poco/ClassLibrary.h"

#include

class PluginA: public AbstractPlugin

{

public:

                   std::stringname() const

                   {

                            return"PluginA";

                   }

};

class PluginB: public AbstractPlugin

{

public:

                   std::stringname() const

                   {

                            return"PluginB";

                   }

};

 

POCO_BEGIN_MANIFEST(AbstractPlugin)

POCO_EXPORT_CLASS(PluginA)

POCO_EXPORT_CLASS(PluginB)

POCO_END_MANIFEST

 

// optional set up and clean up functions

void pocoInitializeLibrary()

{

                   std::cout<< "PluginLibrary initializing" << std::endl;

}

void pocoUninitializeLibrary()

{

                   std::cout<< "PluginLibrary uninitializing" << std::endl;

}

        

          对于使用方来说:

                   //main.cpp

#include "Poco/ClassLoader.h"

#include "Poco/Manifest.h"

#include "AbstractPlugin.h"

#include

typedef Poco::ClassLoaderPluginLoader;

typedef Poco::ManifestPluginManifest;

int main(int argc, char** argv)

{

                   PluginLoaderloader;

                   std::stringlibName("PluginLibrary");

                   libName+= Poco::SharedLibrary::suffix(); // append .dll or .so

                   loader.loadLibrary(libName);

                   PluginLoader::Iteratorit(loader.begin());

                   PluginLoader::Iteratorend(loader.end());

                   for(; it != end; ++it)

                   {

                            std::cout<< "lib path: " << it->first << std::endl;

                            PluginManifest::IteratoritMan(it->second->begin());

                            PluginManifest::IteratorendMan(it->second->end());

                            for(; itMan != endMan; ++itMan)

                            std::cout<< itMan->name() << std::endl;

                   }

                   AbstractPlugin*pPluginA = loader.create("PluginA");

                   AbstractPlugin*pPluginB = loader.create("PluginB");

                   std::cout<< pPluginA->name() << std::endl;

                   std::cout<< pPluginB->name() << std::endl;

                   loader.classFor("PluginA").autoDelete(pPluginA);

                   deletepPluginB;

                   loader.unloadLibrary(libName);

                   return0;

}

                  

         POCO C++库学习和分析 --  线程 (一)

 

        线程是程序设计中用的非常多的技术,在UI设计,网络通讯设计中广泛使用。在POCO库中,线程模块可以分成6个部分去理解。锁(Lock),线程(Thread),主动对象(ActiveObject),线程池(ThreadPool), 定时器(Timer)。下面对它们分别介绍。

 

1.  数据保护-锁

        线程是并行计算中比较复杂的技术之一,使用线程去设计问题时,在获取并行的好处时,也产生了racecondition的问题。锁的存在就是为了解决该问题。

POCO库封装了常见的几种锁,Mutex,Semaphore,Event,Scopelock,ReadWriteLock。类图分别如下:

       Mutex

 

Semaphore

        

                                                       

Event

                                              

ReadWriteLock

                                              

类图非常的简单。就不再多说了,有兴趣的朋友可以自己去看。对于不同平台, POCO基本上选择了比较好的实现方式。比如在Mutex的实现时,Window上用的是criticalsection而非mutex。

 

2.  线程

        POCO对不同操作系统的线程进行了分装,使其变成了一个对象。下面是其的类图:

  熟悉JAVA的朋友一定会很开心,这不就是JAVA中使用线程的两种形式之一吗。所有的业务逻辑全部在Runnable中。Thread类只负责开始(Start)和停止(Join)两个动作。

        来看一下Thread的实现,在C++中底层API (windows下 _beginthreadex, linux下pthread_create)创建线程时必须要求入口函数是个全局或者静态函数,这要求业务具有唯一性。而事实上不同的线程就是为了完成不同业务的,不同对象对应不同线程。那变化时如何被封装至Thread类中呢。答案在下面。

voidThreadImpl::createImpl(Entry ent, void* pData)

{

#ifdefined(_DLL)

      _thread = CreateThread(NULL, _stackSize,ent, pData, 0, &_threadId);

#else

      unsigned threadId;

      _thread = (HANDLE) _beginthreadex(NULL,_stackSize, ent, this, 0, &threadId);

      _threadId =static_cast(threadId);

#endif

      if (!_thread)

           throw SystemException("cannotcreate thread");

      if (_prio != PRIO_NORMAL_IMPL &&!SetThreadPriority(_thread, _prio))

           throw SystemException("cannotset thread priority");

}

 

#ifdefined(_DLL)

DWORDWINAPI ThreadImpl::callableEntry(LPVOID pThread)

#else

unsigned__stdcall ThreadImpl::callableEntry(void* pThread)

#endif

{

      _currentThreadHolder.set(reinterpret_cast(pThread));

#ifdefined(_DEBUG) && defined(POCO_WIN32_DEBUGGER_THREAD_NAMES)

      setThreadName(-1,reinterpret_cast(pThread)->getName().c_str());

#endif

      try

      {

           ThreadImpl* pTI =reinterpret_cast(pThread);

           pTI->_callbackTarget.callback(pTI->_callbackTarget.pData);

      }

      catch (Exception& exc)

      {

           ErrorHandler::handle(exc);

      }

      catch (std::exception& exc)

      {

           ErrorHandler::handle(exc);

      }

      catch (...)

      {

           ErrorHandler::handle();

      }

      return 0;

}

 

在ThreadImpl::createImpl(Entryent, void* pData)函数中创建线程时beginthreadex带入了this指针,也就是线程对象本身。

[cpp] view plaincopy

1.  _thread = (HANDLE) _beginthreadex(NULL, _stackSize, ent, this, 0, &threadId);  

线程对象本身存在一个结构体CallbackData,其中callback指向了真实的业务路口。不同线程对象在初始化时,会被赋值不同的业务入口函数。

而在静态函数callableEntry中,通过调用this指针可以运行真正的业务函数。

[cpp] view plaincopy

1.  ThreadImpl* pTI = reinterpret_cast(pThread);  

2.  pTI->_callbackTarget.callback(pTI->_callbackTarget.pData);  


最后用一段代码实例来结束吧

1.  #include "Poco/Thread.h"  

2.  #include "Poco/Runnable.h"  

3.  #include   

4.  class HelloRunnable: public Poco::Runnable  

5.  {  

6.         virtual void run()  

7.         {  

8.              std::cout << "Hello, world!" << std::endl;  

9.         }  

10.  };  

11.  int main(int argc, char** argv)  

12.  {  

13.         HelloRunnable runnable;  

14.         Poco::Thread thread;  

15.         thread.start(runnable);  

16.         thread.join();  

17.         return 0;  

18.  }  

 

3.  线程池

3.1线程池的基本概念

       首先我们来明确线程池的一些概念。

       什么是线程池?线程池的好处?

       池的英文名:POOL,可以被理解成一个容器。线程池就是放置线程对象的容器。我们知道线程的频繁创建、销毁,是需要耗费一点的系统资源的,如果能够预先创建一系列空线程,在需要使用线程时侯,从线程池里,直接获取IDLE线程,则省去了线程创建的过程,当有频繁的线程出现的时候对性能有比较大的好处,程序执行起来将非常效率。

       什么时候推荐使用线程池?

       很明显,线程越频繁的被创建和释放,越是能体现出线程池的作用。这时候当然推荐使用线程池。

       什么时候不推荐使用线程池?

       推荐线程池使用的反面情况喽。

       比如长时间运行的线程(线程运行的时间越长,其创建和销毁的开销在其生命周期中比重越低)。

       需要永久标识来标识和控制线程,比如想使用专用线程来终止该线程,将其挂起或按名称发现它。因为线程池中的线程都是平等的。

       线程池需要具备的元素

  •        线程池要有列表,可以用来管理多个线程对象。
  •        线程池中的线程,具体执行的内容,可自定义。
  •        线程池中的线程,使用完毕后,还能被收回,供下次使用。
  •        线程池要提供获取空闲(IDLE)线程方法。当然这个方法可以被封装在线程池中,成为其内部接口。

 

3.2 Poco中线程池实现

       先看一看Poco中内存池的类图吧。

 

 对于Poco中的线程池来说,设计上分成了两层。第一层为ThreadPool,第二层为PooledThread对象。 第一层中,ThreadPool负责管理线程池,定义如下:

class ThreadPool

{

public:

              ThreadPool(intminCapacity = 2,

                            intmaxCapacity = 16,

                            intidleTime = 60,

                            intstackSize = POCO_THREAD_STACK_SIZE);

              ThreadPool(conststd::string& name,

                            intminCapacity = 2,

                            intmaxCapacity = 16,

                            intidleTime = 60,

                            intstackSize = POCO_THREAD_STACK_SIZE);

              ~ThreadPool();

              voidaddCapacity(int n);

              intcapacity() const;

              voidsetStackSize(int stackSize);

              intgetStackSize() const;

              intused() const;

              intallocated() const;

              intavailable() const;

              voidstart(Runnable& target);

              voidstart(Runnable& target, const std::string& name);

              voidstartWithPriority(Thread::Priority priority, Runnable& target);

              voidstartWithPriority(Thread::Priority priority, Runnable& target, conststd::string& name);

              voidstopAll();

              voidjoinAll();

              voidcollect();

              conststd::string& name() const;

              staticThreadPool& defaultPool();

 

protected:

              PooledThread*getThread();

              PooledThread*createThread();

 

              voidhousekeep();

 

private:

              ThreadPool(constThreadPool& pool);

              ThreadPool&operator = (const ThreadPool& pool);

 

              typedefstd::vector ThreadVec;

 

              std::string_name;

              int_minCapacity;

              int_maxCapacity;

              int_idleTime;

              int_serial;

              int_age;

              int_stackSize;

              ThreadVec_threads;

              mutableFastMutex _mutex;

};

 

 从ThreadPool的定义看,它是一个PooledThread对象的容器。职责分成两部分:

      第一,维护和管理池属性,如增加线程池线程数目,返回空闲线程数目,结束所有线程

      第二,把需要运行的业务委托给PooledThread对象,通过接口start(Runnable& target)

         void ThreadPool::start(Runnable&target)

{

     getThread()->start(Thread::PRIO_NORMAL,target);

}

 

      函数getThread()为ThreadPool的私有函数,作用是获取一个空闲的PooledThread线程对象,实现如下

         PooledThread* ThreadPool::getThread()

{

         FastMutex::ScopedLock lock(_mutex);

 

         if (++_age == 32)

                   housekeep();

 

         PooledThread* pThread = 0;

         for (ThreadVec::iterator it =_threads.begin(); !pThread && it != _threads.end(); ++it)

         {

                   if ((*it)->idle()) pThread= *it;

         }

         if (!pThread)

         {

                   if (_threads.size() <_maxCapacity)

                   {

            pThread = createThread();

            try

            {

                pThread->start();

                _threads.push_back(pThread);

            }

            catch (...)

            {

                delete pThread;

                throw;

            }

                   }

                   else throwNoThreadAvailableException();

         }

         pThread->activate();

         return pThread;

}

 

 第二层中PooledThread对象为一个在线程池中线程。作为线程池中的线程,其创建于线程池的创建时,销毁于线程池的销毁,生命周期同线程池。在其存活的周期中,状态可分为running task和idle。running状态为正在运行业务任务,idle为线程为闲置状态。Poco中PooledThread继承自Runnable,并且包含一个Thread对象。

class PooledThread: publicRunnable

{

public:

              PooledThread(const std::string& name, int stackSize= POCO_THREAD_STACK_SIZE);

              ~PooledThread();

 

              void start();

              void start(Thread::Priority priority, Runnable&target);

              void start(Thread::Priority priority, Runnable&target, const std::string& name);

              bool idle();

              int idleTime();

              void join();

              void activate();

              void release();

              void run();

 

private:

              volatile bool       _idle;

              volatile std::time_t _idleTime;

              Runnable*           _pTarget;

              std::string         _name;

              Thread              _thread;

              Event               _targetReady;

              Event               _targetCompleted;

              Event               _started;

              FastMutex           _mutex;

};

 

对于PooledThread来说,其线程业务就是不断的检测是否有新的外界业务_pTarget,如果有就运行,没有的话,把自己状态标志位限制,供线程池回收。

void PooledThread::run()

{

         _started.set();

         for(;;)

         {

                   _targetReady.wait();

                   _mutex.lock();

                   if(_pTarget) // a NULL target means kill yourself

                   {

                            _mutex.unlock();

                            try

                            {

                                     _pTarget->run();

                            }

                            catch(Exception& exc)

                            {

                                     ErrorHandler::handle(exc);

                            }

                            catch(std::exception& exc)

                            {

                                     ErrorHandler::handle(exc);

                            }

                            catch(...)

                            {

                                      ErrorHandler::handle();

                            }

                            FastMutex::ScopedLocklock(_mutex);

                            _pTarget  = 0;

#if defined(_WIN32_WCE)

                            _idleTime= wceex_time(NULL);

#else

                            _idleTime= time(NULL);

#endif

                            _idle     = true;

                            _targetCompleted.set();

                            ThreadLocalStorage::clear();

                            _thread.setName(_name);

                            _thread.setPriority(Thread::PRIO_NORMAL);

                   }

                   else

                   {

                            _mutex.unlock();

                            break;

                   }

         }

}

Poco中线程池的实现,耦合性其实是很低的,这不得不归功于其在线程池上两个层次的封装和抽象,类的内聚性非常强的,每个类各干各的事。

3.3 其他

        除了上面线程池的主要属性和接口外,Poco中线程池还实现了一些其他特性。如设置线程运行的优先级,实现了一个默认线程的单件等。

(版权所有,转载时请注明作者和出处 http://blog.csdn.net/arau_sh/article/details/8592579

 

 

4. 定时器

定时器作为线程的扩展,也是编程时经常会被用到的元素。在程序设计上,定时器的作用是很简单。预定某个定时器,即希望在未来的某个时刻,程序能够得到时间到达的触发信号。

编程时,一般对定时器使用有下面一些关注点:

1. 定时器的精度。Poco中的定时器精度并不是很高,具体精度依赖于实现的平台(Windows or Linux)

2. 定时器是否可重复,即定时器是否可触发多次。 Poco中的定时器精度支持多次触发也支持一次触发,由其构造函数Timer决定

              Timer(long startInterval = 0, long periodicInterval =0);

                            /// Creates a new timer object.StartInterval and periodicInterval

                            /// are given in milliseconds. If aperiodicInterval of zero is

                            /// specified, the callback will only becalled once, after the

                            /// startInterval expires.

                            /// To start the timer, call the Start()method.

3. 一个定时器是否可以设置多个时间。 Poco中定时器不支持设置多个时间,每个定时器对应一个时间。如果需要多个时间约定的话,使用者要构造多个定时器。

 

4.1 定时器实现

Poco中的定时器并不复杂,下面是它的类图。

在类图中,Timer继承自Runnable类,也就是说Timer实现了自己的run函数。来看一看,run函数的实现。

void Timer::run()

{

              Poco::Timestamp now;

              long interval(0);

              do

              {

                            long sleep(0);

                            do

                            {

                                          now.update();

                                          sleep =static_cast((_nextInvocation - now)/1000);

                                          if (sleep < 0)

                                          {

                                                        if (interval== 0)

                                                        {

                                                                      sleep= 0;

                                                                      break;

                                                        }

                                                        _nextInvocation+= interval*1000;

                                                        ++_skipped;

                                          }

                            }

                            while (sleep < 0);

 

                            if(_wakeUp.tryWait(sleep))

                            {

                                          Poco::FastMutex::ScopedLocklock(_mutex);

                                          _nextInvocation.update();

                                          interval =_periodicInterval;

                            }

                            else

                            {

                                          try

                                          {

                                                        _pCallback->invoke(*this);

                                          }

                                          catch (Poco::Exception&exc)

                                          {

                                                        Poco::ErrorHandler::handle(exc);

                                          }

                                          catch (std::exception&exc)

                                          {

                                                        Poco::ErrorHandler::handle(exc);

                                          }

                                          catch (...)

                                          {

                                                        Poco::ErrorHandler::handle();

                                          }

                                          interval =_periodicInterval;

                            }

                            _nextInvocation += interval*1000;

                            _skipped = 0;

              }

              while (interval > 0);

              _done.set();

}

在run函数中,我们发现定时器的业务就是不断更新下一次触发时间,并通过睡眠等待到预定时间,触发调用者业务。

 

4.2 定时器使用

最后让我们来看一个定时器的例子:

#include"Poco/Timer.h"

#include"Poco/Thread.h"

using Poco::Timer;

using Poco::TimerCallback;

 

class TimerExample

{

              public:

              void onTimer(Poco::Timer& timer)

              {

                            std::cout << "onTimercalled." << std::endl;

              }

};

 

int main(int argc, char**argv)

{

              TimerExample te;

              Timer timer(250, 500); // fire after 250ms, repeatevery 500ms

              timer.start(TimerCallback(te,&TimerExample::onTimer));

              Thread::sleep(5000);

              timer.stop();

              return 0;

}

 

5. 主动对象

5.1 线程回顾

        在讨论主动对象之前,我想先说一下对于Poco中多线程编程的理解。大家都知道,对于多线程编程而言最基本的元素只有两个数据:锁和线程。线程提高了程序的效率,也带来了数据的竞争,因此为了保证数据的正确性,孪生兄弟"锁"随之产生。

        对于不同的操作系统和编程语言而言,线程和锁通常是以系统API的方式提供的,不同语言和不同操作系统下API并不相同,但线程和锁的特性是一致的,这也是对线程和锁进行封装的基础。比如所有的系统线程API都提供了线程开始函数,其中可以设置线程的入口函数,提供了线程终止等功能。用面对对象的思想对线程和锁进行封装后,线程和锁就可以被看成编程时的一个基本粒子,一堆积木中的一个固定模块,用来搭建更大的组件。

        除了线程和锁这两个基本模块之外,定时器和线程池也比较常用。线程池多用作线程频繁创建的时候。在Poco中,把线程池封装成为一个对象,池中的线程在池存在时始终存活,只不过是线程状态有所不同,不是运行中就是挂起。如果把线程看成一种资源的话,线程资源的申请和释放被放入了线程池的构造和析构函数中,Poco的这种封装也就是C++推荐的方法。
       在Poco的线程池实现中,ThreadPool类还提供了一个线程池的单件接口。这个由静态函数定义:

staticThreadPool& defaultPool();

                通过这个函数,使用者可以很方便的从Poco库中获取一个线程的起点,而无需关心线程维护的细节,这样使用者可以进一步把注意力放在需要实现的业务上。在实现了ThreadPool的这个接口后,Poco类中关于线程的更高级封装即可以实现。如定时器(Timer),主动对象(ActivityObject),任务(Task)。
        在Poco实现定时器,实现ThreadPool中的PooledThread,以及接下来要讨论的主动对象中的ActiveRunnable,RunnableAdapter,ActiveDispatcher时,可以发现这些类都从Runnable继承。这些类需要实现自己的run函数,只不过在run函数中做的工作不同。

       定时器中的run函数工作就是计时,定期更新实现,至触发时刻运行使用者定义的用户事件。而PooledThread的工作则是,控制线程状态,在挂起和运行间切换,当有用户业务需要运行时,运行用户业务。说穿了这些类都是用户业务的一个代理。只不过代理时,实现的手段不同。

5.2 主动对象

        总结了前几章后,让我们继续往下看一下主动对象。首先是什么是主动对象。Poco中对于主动对象有如下描述:
        主动对象是一个对象,这个对象使用自己线程运行自己的成员函数。
        1.  在Poco中,主动对象支持两种主动成员函数。
        2. Activity类型的主动对象使用在用户业务为不需要返回值和无参数的成员函数时侯。
        3. ActiveMethod类型的主动对象使用在用户业务为需要返回值和需要参数的成员函数时侯。
        4.  所有的主动对象即能够共享一个单线程,也可以拥有其自己的线程。
        事实上在Poco库中实现了3种类型的主动对象,分别是Activity、ActiveMethod、ActiveDispatcher。其中ActiveDispatcher可以看成是ActiveMethod的一个变种。Activity和ActiveMethod的区别在于是否需要关心用户业务的返回值,因为ActiveMethod模板实现时也特化了一个没有用户参数的版本。
        其实我们自己也能很容易的实现一个主动对象。简单的接口如下:

         classMyObject : public Runnable

{

public:

     voidstart();

     voidstop();

     voidrun();

public:

     voidrealrun();

public:

     Thread_thread;

};

void MyObject::start()

{

     _thread.start();

}

void MyObject::stop()

{

     _thread.join();

}

void MyObject::run()

{

     this->realrun();

}

 从上面分析一下,实现一个主动对象需要些什么:
        1.  一个线程驱动。在上例中主动对象包含了一个Thread对象
        2.  用户真实业务(在上例中由函数MyObject::realrun提供),在继承自Runnabled的封装类的run函数中被调用。
        Poco中主动对象要做的事情是很类似的,但是Poco提供的是一个框架,供开发者使用的框架。这个框架即需要满足用户需求,坚固,还要求便于使用。为了实现这个框架需要一些编程上的技巧。对于Poco的开发者而言,使用Poco库的人就是他们的用户。使用者必须很容易的通过Poco库把自己的类变成一个主动对象,而Poco的开发者很明显并不知道用户会如何定义一个的类。所以实现这样一种可变的结构,C++语言最适合方法的无疑是泛型编程了。下面我们来具体说一下Poco中的主动对象。

5.3 Activities

        首先来看一下Activities的特性:
        1. Activities能够在对象构造时自动启动,也能够稍后手动启动
        2. Activities能够在任何时候被停止。为了完成这个工作,isStopped()成员函数必须周期性的被调用。
        3. Activities主动对象运行的成员函数不能够携带参数和返回值
        4. Activities的线程驱动来自于默认的线程库
        来看一下Activities的类图:

从类图中我们可以看到Activity的线程驱动来自于默认的线程库。在Activity中为了调用到用户的真实业务函数,需要把对象实例和类的函数入口传进Activity中。这在Activity的构造函数中实现。

Activity(C*pOwner, Callback method):

     _pOwner(pOwner),

     _runnable(*pOwner, method),

     _stopped(true),

     _running(false),

     _done(false)

/// Creates theactivity. Call start() to

/// start it.

{

     poco_check_ptr (pOwner);

}

 通过泛型,pOwner可以指向任何外界定义的实例。Activity由于包含了线程驱动,在start()中调用了ThreadPool::defaultPool().start(*this),所以对于调用者而言,可以被看成一个线程驱动。
        RunnableAdapter看名字就是一个适配类,用于存储调用对象的指针和调用类的入口函数。

#include"Poco/Activity.h"

#include"Poco/Thread.h"

#include

usingPoco::Thread;

classActivityExample

{

public:

      ActivityExample(): _activity(this,

         &ActivityExample::runActivity)

      {}

      void start()

      {

           _activity.start();

      }

      void stop()

      {

           _activity.stop(); // request stop

           _activity.wait(); // wait untilactivity actually stops

      }

protected:

      void runActivity()

     {

          while (!_activity.isStopped())

         {

              std::cout << "Activityrunning." << std::endl;

              Thread::sleep(200);

         }

      }

private:

      Poco::Activity _activity;

};

 

int main(intargc, char** argv)

{

      ActivityExample example;

      example.start();

      Thread::sleep(2000);

      example.stop();

      return 0;

}

 在上例中,可以看到使用类ActivityExample包容了一个_activity对象。为了能够在任何时刻停止,在ActivityExample的真实业务runActivity()函数中,定期调用了_activity.isStopped()函数。

5.4 Active Methods

        来看一下Poco中Active Methods的特性:
        Active Methods主动对象拥有一个能在自身线程中运行的包含参数和返回值成员函数方法,其线程驱动也来自于默认线程池。
        1. 主动对象能够共享一个线程。当一个主动对象运行时,其他的对象等待。
        2. 运行业务的成员函数可以拥有一个参数并能返回一个值。
        3. 如果函数需要传递更多的参数,可以使用结构体、std::pair、或者Poco::Tuple.
        4. ActiveMethods主动对象的结果由Poco::ActiveResult 提供。
        5. 通常主动对象的函数的返回值不会在调用函数后立刻返回,所以在设计时设计了Poco::ActiveResult类。
        6. ActiveResult是一个模板类,在函数返回结果时被创建。
        7. ActiveResult的返回结果可能是一个需要的结果,也可能是一个异常。
        8. 使用者通过ActiveResult::wait()函数等待到结果,通过ActiveResult::data()获取真实返回值。


        抛开上面文档中提到的Active Methods的特性不提。Active Methods和Activities的区别在于Active Methods调用的自身函数拥有返回值和参数。也就是说,在Active Methods中类对象、类对象的入口函数、返回值和传入参数都是未定的。所以用泛型实现时,ActiveMethod定义如下:

template >

classActiveMethod

{

// ...

}

其中ResultType代表了返回值的共性,ArgType代表了输入参数的共性,OwnerType代表了业务调用拥有者的共性,StarterType代表了线程驱动的共性。类对象的入口函数则被包装进入另一个类ActiveRunnable中,其定义如下:

template

class ActiveRunnable: publicActiveRunnableBase

{

// ...

}

让我们来看一个Active Methods的例子:

#include"Poco/ActiveMethod.h"

#include"Poco/ActiveResult.h"

#include

using Poco::ActiveMethod;

using Poco::ActiveResult;

class ActiveAdder

{

public:

     ActiveAdder():

       add(this, &ActiveAdder::addImpl)

     {}

     ActiveMethod, ActiveAdder> add;

private:

     int addImpl(const std::pair& args)

     {

          return args.first + args.second;

     }

};

 

int main(int argc, char** argv)

{

     ActiveAdder adder;

     ActiveResult sum = adder.add(std::make_pair(1, 2));

     sum.wait();

     std::cout << sum.data() << std::endl;

     return 0;

}

 在上面这个例子中ActiveMethod初始化方式也和Activity类似,ActiveAdder拥有一个ActiveMethod对象,在构造时对add进行实例化,传入了业务调用对象实例的指针,调用对象函数的入口地址。

       不同的地方是:

       1. 让我们来仔细观察下面这行代码:

ActiveResult sum =adder.add(std::make_pair(1, 2));

 这行代码是立刻返回的,sum此时并没有得到真实的运行结果。只有等到sum.wait()返回,真实的运算结果才被放置于sum中。
        正是由于ActiveResultsum = adder.add(std::make_pair(1, 2))立刻返回,才让出了执行的主线程,让多线程的威力显现出来。这和把等待任务函数放在Activities中不同,ActiveMethod由于有结果交换的过程,其等待函数放于结果返回值类ActiveResult中。

2.      为了传入参数和传出结果,通过泛型规范了其定义

ActiveMethod, ActiveAdder>

3. 深度封装了线程,因为从调用方来看根本无法看到线程的影子。没有start,stop等函数. 
        第二点很有意思,能够实现这一点,在于在ActiveMethod中重载了操作符().下面是其定义:

ActiveResultTypeoperator () (const ArgType& arg)

///Invokes the ActiveMethod.

{

      ActiveResultType result(newActiveResultHolder());

      ActiveRunnableBase::Ptr pRunnable(newActiveRunnableType(_pOwner, _method, arg, result));

      StarterType::start(_pOwner, pRunnable);

      return result;

}

上面这个操作符就是线程的起点。 StarterType::start(_pOwner,pRunnable)定义如下:

staticvoid ActiveStarter::start(OwnerType* pOwner, ActiveRunnableBase::Ptr pRunnable)

{

   ThreadPool::defaultPool().start(*pRunnable);

    pRunnable->duplicate(); // The runnablewill release itself.

}

在调用StarterType::start(_pOwner,pRunnable)后,接下来会进入ActiveRunnableType对象的run函数。ActiveRunnable::run()函数定义为:

voidrun()

{

    ActiveRunnableBase::Ptr guard(this, false);// ensure automatic release when done

    try

    {

        _result.data(new ResultType((_pOwner->*_method)(_arg)));

    }

    catch (Exception& e)

    {

       _result.error(e);

    }

    catch (std::exception& e)

    {

      _result.error(e.what());

    }

    catch (...)

   {

     _result.error("unknownexception");

   }

   _result.notify();

}

 语句_result.data(newResultType((_pOwner->*_method)(_arg)))完成了用户函数的真实调用。
        那线程的停止呢。这个被封装到ActiveResult中,ActiveResult::wait()定义如下:

voidActiveResult::wait()

///Pauses the caller until the result becomes available.

{

   _pHolder->wait();

}

 于是整个流程呼之欲出。

  ActiveMethods的类图如下:

5.5 ActiveDispatcher

       ActiveDispatcher的特性如下:
        1. ActiveMethod的默认行为并不符合经典的主动对象概念,经典的主动对象定义要求主动对象支持多个方法,并且各方法能够在单线程中被顺序执行。
        2. 为了实现经典主动对象的行为,ActiveDispatcher被设计成主动对象的基类。
        3. 通过使用ActiveDispatcher可以顺序执行用户业务函数。
        让我们来看一下ActiveDispatcher的类图:

  在类图中用户业务被打包成为MethodNotification对象放入了ActiveDispatcher的queue容器中,然后被顺序执行。其中Notification和NotificationQueue我们在以后介绍。
        下面是其一个实现的例子:

#include "Poco/ActiveMethod.h"

#include "Poco/ActiveResult.h"

#include "Poco/ActiveDispatcher.h"

#include

using Poco::ActiveMethod;

using Poco::ActiveResult;

class ActiveAdder: public Poco::ActiveDispatcher

{

public:

      ActiveObject():add(this, &ActiveAdder::addImpl)

      {}

     ActiveMethod, ActiveAdder,

     Poco::ActiveStarter > add;

private:

     int addImpl(conststd::pair& args)

     {

         returnargs.first + args.second;

     }

};

 

int main(int argc, char** argv)

{

     ActiveAdder adder;

    ActiveResult sum = adder.add(std::make_pair(1, 2));

     sum.wait();

     std::cout <

     return 0;

}

5.6 其他

    在讨论完Poco主动对象的实现侯,回过来我们再讨论一下为什么要使用主动对象,使用Poco中主动对象的好处。

        使用主动对象的好处很明显,能够使业务的划分更加清晰。但是我并不推荐在真实的项目中使用Poco封装的主动对象,除非是一些非常简单的场景。原因如下:

 1.在真实的业务应用中,我们能够很容易的在Thread和runnable基础上自己封装一个主动对象。
        2. 真实业务不需要抽象,业务都是具体的。不使用主动对象的可读性会更加好。
        虽然我不推荐在真实项目中使用Poco的主动对象,但是我觉得学习Poco中主动对象的实现仍然很有意义,特别是泛型和其他一些编程技巧。

 

POCO C++库学习和分析 -- 任务

 

1. 任务的定义

        任务虽然在Poco::Foundation库的目录结构中被单独划出,其实也可以被看成线程的应用,放在线程章节。首先来看一下Poco中对于任务的描述:

  • task主要应用在GUI和Seerver程序中,用于追踪后台线程的进度。
  • 应用Poco任务时,需要类Poco::Task和类Poco::TaskManager配合使用。其中类Poco::Task继承自Poco::Runnable,它提供了接口可以便利的报告线程进度。Poco::TaskManager则对Poco::Task进行管理。
  • 为了完成取消和上报线程进度的工作:

                 a. 使用者必须从Poco::Task创建一个子类并重写runTask()函数
                 b. 为了完成进度上报的功能,在子类的runTask()函数中,必须周期的调用setProgress()函数去上报信息
                 c. 为了能够在任务运行时终止任务,必须在子类的runTask()函数中,周期性的调用isCancelled()或者sleep()函数,去检查是否有任务停止请求
                 d. 如果isCancelled()或者sleep()返回真,runTask()返回。

  • Poco::TaskManager通过使用Poco::NotificationCenter 去通知所有需要接受任务消息的对象

        从上面描述可以看出,Poco中Task的功能就是能够自动汇报线程运行进度。

 

2. 任务用例

       Task的应用非常简单,下面是其一个使用例子:

#include "Poco/Task.h"

#include "Poco/TaskManager.h"

#include "Poco/TaskNotification.h"

#include "Poco/Observer.h"

 

using Poco::Observer;

class SampleTask: public Poco::Task

{

public:

         SampleTask(conststd::string& name): Task(name)

         {}

 

 

         voidrunTask()

         {

                   for(int i = 0; i < 100; ++i)

                   {

                            setProgress(float(i)/100);// report progress

                            if(sleep(1000))

                                     break;

                   }

         }

};

 

class ProgressHandler

{

public:

         voidonProgress(Poco::TaskProgressNotification* pNf)

         {

                   std::cout<< pNf->task()->name()

                            <<" progress: " << pNf->progress() << std::endl;

                   pNf->release();

         }

         voidonFinished(Poco::TaskFinishedNotification* pNf)

         {

                   std::cout<< pNf->task()->name() << " finished." <

                   pNf->release();

         }

};

 

int main(int argc, char** argv)

{

         Poco::TaskManagertm;

         ProgressHandlerpm;

         tm.addObserver(

                   Observer

                   (pm,&ProgressHandler::onProgress)

                   );

         tm.addObserver(

                   Observer

                   (pm,&ProgressHandler::onFinished)

                   );

         tm.start(newSampleTask("Task 1")); // tm takes ownership

         tm.start(newSampleTask("Task 2"));

         tm.joinAll();

         return0;

}

3. Task类图

        最后给出Poco中Task的类图。

 在类图中,我们可以看到Task类继承自RefCountedObject,这主要是为了Task类的管理。TaskManger对Task实现了自动的垃圾收集。

 

POCO C++库学习和分析 -- 内存管理 (一)

        对于内存的管理,Poco C++库中主要包含了引用计数,智能指针,内存池等几个部分。下面将分别对这几个部分进行介绍。首先回顾一下,对于内存的管理,出现过的几种技术。C时代的内存池,主要解决内存碎片,和内存的频繁获取和释放的开销问题。到了C++时代,内存池仍然存在,但是出现了面对对象分配的内存池,解决问题还是一样。C++中智能指针,如STL中的auto_ptr,boost库中share_ptr等。通过把堆上对象的委托给智能指针(智能指针本身可以看成是一个栈对象),并在智能指针内部实现引用计数,当引用计数为0时,删除堆对象,从而达到让编译器自动删除堆对象的目的,实现了堆对象的自动管理。Java和C#的垃圾收集,在语言层次分装,所有的对象都在堆上分配,然后交由语言本身管理,程序员无需关心对象内存的释放。

1. 引用计数和智能指针概述:

       对于C和C++来说,堆上内存的管理是交由程序员完成的,程序员如果在堆上分配了一块内存,就必须负责释放掉。如果不小心,就会造成内存泄露。因此所有C/C++程序员设计程序时,对指针和内存的管理都会如履薄冰,非常的小心。而Java和C#则不同,虽然所有对象都被放在堆上,但由于语言本身存在垃圾收集机制,程序员不再需要关心对象的释放。这或多或少的能够使程序员更多的把精力放在其业务编程上。

       讲到这里,就顺便扯开去,讲一些题外话。对于某些编程技术讨论时的一些看法。如同制造业一样,制造业存在很多种类,对制造业的划分方法当然也很多。在制造业中存在一个特殊的种类,装备制造业。也就是制造机器的制造业。对于程序员来说也是一样,绝大多数程序员都是面对业务进行编程的,而极少数程序员则是为了制造编程工具或者提供更方便的编程方法而编制程序。这个区别往往导致,不同程序员看问题的角度不同,结果当然也不同。我想很多时候,问题的答案都是不唯一的。接下去继续讨论Poco吧。

       通过引用计数和智能指针机制,C++也可以完成了某种意义上的垃圾收集的工作。程序员通过使用智能指针,同样不需要再关注堆对象的释放,当然胶水代码还是需要的。在Poco库中存在两种智能指针,AutoPtr和SharedPtr。

1.1 引用计数(Reference Counting)

        Poco中对于引用计数,是如此描述的。“Reference counting is atechnique of storing the number of references, pointers, or handles to aresource such as an

object or block of memory. It istypically used as a means of deallocating objects which are no longerreferenced. --Wikipedia”。

翻译过来即"引用计数是一项用来存储指向某个对象或者内存块的引用、指针或句柄的数量的计数。这项技术通常被用于对象不再存在任何引用关系时的释放 -- wikipedia"。

        对于引用计数有如下特点:

        1. 当一个对于对象的引用被销毁或者覆盖时,这个对象的引用计数的数量减少。

        2. 当一个对于对象引用被创建或者拷贝时,这个对象的引用计数数量增加。

        3. 初始化对象的引用,其引用对象的引用计数为1.

        4. 当对对象的引用计数为0时,对象被删除。

        5. 在多线程场景下,对引用计数的增加、减少和比较操作必须为原子操作.

1.2 对象所有权(Object Ownership)

 对象所有权有如下特点:

        1. 拥有动态对象所有权的某个所有者,当动态对象不在被需要时,必须负责删除动态对象。

        2. 如果动态对象的所有者删除动态对象失败,将导致程序内存泄露。

        3. 指向动态对象的其他物体,不能删除该动态对象。

        4. 动态对象的所有权是可传递的,但在任何给定时刻,动态对象只有一个所有者

1.3 引用计数和对象所有权的关系

        二者关系如下:

        1.  一个指针获取到引用计数对象的所有权时,不会增加引用计数的数目。分两种情况来讨论:

            a)  引用计数对象原先不存在所有者(换句话说,引用计数对象刚刚被创建)

            b)  引用计数对象原先存在所有者,由于原先的所有者放弃了对对象的所有权,所以新所有者获取对象所有权的动作并不会增加引用计数数目。

         2. 通常,第一个在对象创建后被对象赋值的指针拥有拥有权,其他的则没有

 

2 AutoPtr

2.1 Poco中AutoPtr的例子

         首先来看AutoPtr使用的一个例子:

 

#include"Poco/AutoPtr.h"

#include"Poco/RefCountedObject.h"

class A: publicPoco::RefCountedObject

{

};

int main(int argc, char**argv)

{

     Poco::AutoPtr p1(new A);

     A* pA = p1;

     // Poco::AutoPtr p2(pA); // BAD!p2 assumes sole ownership

     Poco::AutoPtr p2(pA, true); //Okay: p2 shares ownership with p1

     Poco::AutoPtr p3;

     // p3 = pA; // BAD! p3 assumes soleownership

     p3.assign(pA, true); // Okay: p3 sharesownership with p1

     return 0;

}

  RefCountedObject是什么呢?其定义如下:

class Foundation_APIRefCountedObject

              /// A base class for objects that employ

              /// reference counting based garbage collection.

              ///

              /// Reference-counted objects inhibit construction

              /// by copying and assignment.

{

public:

              RefCountedObject();

                            /// Creates the RefCountedObject.

                            /// The initial reference count is one.

 

              void duplicate() const;

                            /// Increments the object's referencecount.

                           

              void release() const;

                            /// Decrements the object's referencecount

                            /// and deletes the object if the count

                            /// reaches zero.

                           

              int referenceCount() const;

                            /// Returns the reference count.

 

protected:

              virtual ~RefCountedObject();

                            /// Destroys the RefCountedObject.

 

private:

              RefCountedObject(const RefCountedObject&);

              RefCountedObject& operator = (constRefCountedObject&);

 

              mutable AtomicCounter _counter;

};

RefCountedObject原来是一个引用计数对象,其中封装了原子计数类AtomicCounter。实现了两个接口,其中duplicate()用来增加引用计数数目,每次调用引用计数增加1;release()用来减少引用计数数目,每次调用引用计数减少1.

        事实上AutoPtr支持一切实现duplicate()和release()的引用计数对象。下面是另外一个例子:

#include "Poco/AutoPtr.h"

using Poco::AutoPtr;

class RCO

{

public:

      RCO(): _rc(1)

      {

      }

 

      void duplicate()

      {

              ++_rc;                                                   // Warning: not thread safe!

      }

 

      void release()

      {

              if (--_rc == 0) delete this;                             // Warning: notthread safe!

      }

 

private:

      int _rc;

};

 

int main(int argc, char** argv)

{

      RCO* pNew = new RCO;                                    // _rc == 1

      AutoPtr p1(pNew);                                  // _rc == 1

      AutoPtr p2(p1);                                    // _rc == 2

      AutoPtr p3(pNew, true);                            // _rc == 3

      p2 = 0;                                                // _rc == 2

      p3 = 0;                                                // _rc == 1

      RCO* pRCO = p1;                                         // _rc== 1

      p1 = 0;                                                // _rc == 0 -> deleted

 

      // pRCO and pNew now invalid!

      p1 = new RCO;                                           //_rc == 1

      return 0;

}

                                                              // _rc == 0 -> deleted

 

2.2 Poco中AutoPtr的类图

从类图中可以看出,Poco中AutoPtr类是和RefCountedObject是配套使用的,如果用户类继承自RefCountedObject,就可以由AutoPtr实现垃圾收集。

2.3 Poco中AutoPtr的说明和注意事项

         Poco::AutoPtr实现了一个引用计数对象的智能指针,能够实例化任何支持引用计数的类。符合下列要求的类可以被定义成为支持引用计数:

        1. 这个类必须存在引用计数,在对象被创建时,引用计数被初始化值为1

        2. 这个类必须支持duplicate()接口增加引用计数

        3. 这个类必须支持release()接口减少引用计数,并且在引用计数为0时,删除类对象。

 

        Poco::AutoPtr的操作符合1.2节中对于“对象所有权(Object Ownership)”的描述:

        1. 当AutoPtr从原生指针C*构造时,AutoPtr获取到对象C的所有权(对象的引用计数为1,保持不变)。

        2. 当使用赋值操作符“=”把原生指针C*赋予AutoPtr时,AutoPtr 获取到对象C的所有权(新对象的引用计数保持不变)。下面是AutoPtr关于这个实现:

 

AutoPtr& assign(C* ptr)

{

              if(_ptr != ptr)

              {

                            if(_ptr) _ptr->release();

                            _ptr= ptr;

              }

              return*this;

}

 

AutoPtr& operator = (C* ptr)

{

              returnassign(ptr);

}

 3. 当AutoPtr从另一AutoPtr构造时,两个AutoPtr共享一个C的拥有权,引用计数增加

       4. 当使用赋值操作符“=”把AutoPtr赋予另一个AutoPtr时,两个AutoPtr 共享一个C的拥有权,引用计数增加

 

       Poco::AutoPtr的操作符与值语义:

       1. Poco::AutoPtr 支持关系表达式"==","!=", "<", "<="," >",">="

       2. 当Poco::AutoPtr 中的原生指针为空时,使用"*"和"->"操作符,将抛出异常“NullPointerException”

       3. Poco::AutoPtr 支持所有的值语义函数(默认构造函数、拷贝构造函数、赋值操作符),并且能够被各种容器所使用(如std::vector、std::map等)

       4. 使用AutoPtr::isNull()或者AutoPtr::operator ! ()可以测试其内部原生指针是否为空

      
        Poco::AutoPtr与转换函数:

       1. 和原生指针一样, Poco::AutoPtr支持转换操作。其定义如下:

template

AutoPtr cast()const

              /// Casts the AutoPtr via a dynamic cast to the giventype.

              /// Returns an AutoPtr containing NULL if the castfails.

              /// Example: (assume class Sub: public Super)

              ///   AutoPtr super(new Sub());

              ///   AutoPtr sub = super.cast();

              ///   poco_assert (sub.get());

{

              Other* pOther = dynamic_cast(_ptr);

              return AutoPtr(pOther, true);

}

2. AutoPtr的cast总是安全的,因为其内部使用了dynamic_cast,因此如果转换非法只会导致一个空指针。

 3. AutoPtr的赋值函数的兼容性通过模板构造函数和赋值操作符来支持。其定义如下:

template

AutoPtr(constAutoPtr& ptr): _ptr(const_cast(ptr.get()))

{

       if (_ptr) _ptr->duplicate();

}

template

AutoPtr& assign(constAutoPtr& ptr)

{

       if (ptr.get() != _ptr)

       {

                     if (_ptr) _ptr->release();

                     _ptr = const_cast(ptr.get());

                     if (_ptr) _ptr->duplicate();

       }

       return *this;

}

template

AutoPtr& operator =(const AutoPtr& ptr)

{

       return assign(ptr);

}

 注意事项和陷阱:

       当使用赋值操作符“=”把一个AutoPtr赋给一个原生指针,然后再把这个原生指针赋予另个AutoPtr时要非常小心。这时候两个AutoPtr'都会声称拥有对象的所有权。这是非常坏的一件事情。必须明确的告知AutoPtr需要分享对象的所有权。使用下列两个函数可以解决问题:

              AutoPtr::AutoPtr(C*pObject, bool shared);

AutoPtr&AutoPtr::assign(C* pObject, bool shared);

 其中shared值必须为true。下面是一个样例:

#include"Poco/AutoPtr.h"

#include "Poco/RefCountedObject.h"

class A: publicPoco::RefCountedObject

{

};

int main(int argc, char**argv)

{

         Poco::AutoPtr p1(new A);

         A* pA = p1;

         // Poco::AutoPtr p2(pA);             // BAD! p2 assumes sole ownership

         Poco::AutoPtr p2(pA,true);          // Okay: p2 sharesownership with p1

         Poco::AutoPtr p3;

         // p3 = pA;                             // BAD! p3 assumessole ownership

         p3.assign(pA, true);                    // Okay: p3 sharesownership with p1

         return 0;

}

2.4 其他

        很明显Poco中的AutoPtrSTLauto_ptr是不同,Stlauto_ptr某种意义上是一个Scope ptr,其实现并不依赖引用计数,它和boost库中的scoped_ptr很相似。而Poco中的AutoPtrboost库中的intrusive_ptr是类似的,基本上可以看做是同一东西。

 

POCO C++库学习和分析 -- 内存管理 (二)

 

3. SharedPtr

       SharedPtr是Poco库中基于引用计数实现的另外一种智能指针。同AutoPtr相比,Poco::SharedPtr主要用于为没有实现引用计数功能的类(换句话说,也就是该类本身不是引用计数对象)提供引用计数服务,实现动态地址的自动回收。

        可以这么说,Poco::AutoPtr是使用继承关系来实现的智能指针,而Poco::SharedPtr是聚合方法实现的智能指针。

3.1 SharedPtr的类图

        首先来看一下SharedPtr的类图:

从类图中可以看到SharedPtr是对引用计数和原生指针封装。其中有成员指针_ptr,指向任意类型的C;同时还存在一个引用计数对象的指针_pCounter,指向任意一个实现了引用计数的类。当然在Poco库中提供了ReferenceCount的默认实现,类ReferenceCounter。

       比较类ReferenceCounter和AutoPtr中依赖的类RefCountedObject,可以发现其实现相同,本质上就是一个东西。Poco库中之所以把两者分开,我想是为了明确的表示类与类之间的关系。ReferenceCounter用于组合,而RefCountedObject用于继承。

       SharedPtr在实现模板的时候,还预留了RP参数,这是一个释放策略,用于调整SharedPtr在释放数组和单个对象之间不同策略的转换。

         template >

class SharedPtr

{

         // ...

}

其中C为对象原生指针,RC为SharedPtr管理的引用计数对象,RP为内存释放策略。

3.2 SharedPtr操作符和值语义

       1. Poco::SharedPtr同样支持关系操作符==, !=, <,<=, >, >=;

        2. 当Poco::SharedPtr中原生指针为空时,使用解引用操作符“*”或者"->",Poco::SharedPtr会抛出一个NullPointerException 异常。

       3. Poco::SharedPtr同样支持全值语义,包括默认构造函数,拷贝构造函数,赋值函数并且同样可以用于各类容器(如std::vector 和 std::map)

SharedPtr& operator = (C* ptr)

{

         returnassign(ptr);

}

 

SharedPtr& assign(C* ptr)

{

         if(get() != ptr)

         {

                   RC*pTmp = new RC;

                   release();

                   _pCounter= pTmp;

                   _ptr= ptr;

         }

         return*this;

}

 

void release()

{

         poco_assert_dbg(_pCounter);

         inti = _pCounter->release();

         if(i == 0)

         {

                   RP::release(_ptr);

                   _ptr= 0;

 

                   delete_pCounter;

                   _pCounter= 0;

         }

}

注意,在SharedPtr赋值操作符"="中的操作,对于原生指针_ptr的操作策略是交换,而引用计数对象_pCounter的策略是先new一个,再交换。

       4. 可以用SharedPtr::isNull()和SharedPtr::operator ! () 去检查内部的原生指针是否为空。

3.3 SharedPtr和Cast类型转换

        同普通指针类似,Poco::SharedPtr支持cast操作符。这在 templateSharedPtr cast() const中实现,其定义如下:

template

SharedPtr cast() const

         ///Casts the SharedPtr via a dynamic cast to the given type.

         ///Returns an SharedPtr containing NULL if the cast fails.

         ///Example: (assume class Sub: public Super)

         ///    SharedPtr super(new Sub());

         ///    SharedPtr sub =super.cast();

         ///    poco_assert (sub.get());

{

         Other*pOther = dynamic_cast(_ptr);

         if(pOther)

                   returnSharedPtr(_pCounter, pOther);

         returnSharedPtr();

}

Poco::SharedPtr中类型转换总是安全的,在其内部实现时,使用了dynamic_cast,所以一个不合法的转换,会导致原生指针为空。

       Poco::SharedPtr中赋值操作符的兼容性通过构造函数和赋值操作符共同完成。

template

SharedPtr& operator = (constSharedPtr& ptr)

{

         returnassign(ptr);

}

 

template

SharedPtr& assign(const SharedPtr& ptr)

{

         if(ptr.get() != _ptr)

         {

                   SharedPtrtmp(ptr);

                   swap(tmp);

         }

         return*this;

}

 

template

SharedPtr(const SharedPtr& ptr): _pCounter(ptr._pCounter),_ptr(const_cast(ptr.get()))

{

         _pCounter->duplicate();

}

下面是关于操作符的一个例子:

#include "Poco/SharedPtr.h"

class A

{

public:

         virtual~A()

         {}

};

class B: public A

{

};

class C: public A

{

};

int main(int argc, char** argv)

{

         Poco::SharedPtrpA;

         Poco::SharedPtrpB(new B);

         pA= pB;                         // okay, pBis a subclass of pA

         pA= new B;

         //pB = pA;                     // will notcompile

         pB= pA.cast();              //okay

         Poco::SharedPtrpC(new C);

         pB= pC.cast();              // pBis null

         return0;

}

 

AutoPtr::AutoPtr(C* pObject, bool shared); 

AutoPtr& AutoPtr::assign(C* pObject, boolshared); 

对于Poco::SharedPtr来说,最好的方法是一旦用SharedPtr获取到对象所有权后,就不要再试图使用指向对象的原生指针。

      下面是SharedPtr的一个例子:

#include"Poco/SharedPtr.h"

#include

#include

using Poco::SharedPtr;

int main(int argc, char**argv)

{

              std::string* pString = new std::string("hello,world!");

              Poco::SharedPtr p1(pString);                  // rc == 1

              Poco::SharedPtr p2(p1);                       // rc == 2

              p2 = 0;                                                   // rc == 1

              // p2 = pString;                                           // BADBAD BAD: multiple owners -> multiple delete

              p2 = p1;                                                  // rc == 2

              std::string::size_type len = p1->length();                 // dereferencing with ->

              std::cout << *p1 << std::endl;                             // dereferencing with *

              return 0;

}

// rc == 0 -> deleted

3.5 SharedPtr和数组

       默认的SharedPtr删除策略是指删除对象。如果创建对象时使用数组,并把它委托给SharedPtr,必须使用对应数组删除策略。这时候SharedPtr的模板参数中ReleasePolicy应该使用类ReleaseArrayPolicy。

      下面是对应的另一个例子:

3.6 其他

      同boost库比较的话,Poco中的SharedPtr同boost库中的shared_ptr可以说是类似的,行为上相似,虽然实现不同。

 

 

POCO C++库学习和分析 -- 内存管理 (三)

   看完Poco库中的智能指针,基本上Poco中的内存管理已经快结束了。其他的部分都是些边边角角的东西,非常的简单。下面一一介绍。

4. AutoReleasePool

        AutoReleasePool类的出现也同样是为了解决用户动态分配对象的释放问题,但同智能指针AutoPtr和SharedPtr通过把堆上的对象包装成栈对象,再通过引用计数在类的析构函数中实现自动删除对象的解决方案不同是,其策略为构造一个容器,用来存储动态对象的指针,在AutoReleasePool析构函数中统一释放。

        这个过程和java语言中的垃圾收集机制是类似的,只不过AutoReleasePool实现的非常简单,在AutoReleasePool销毁时释放资源,而在java语言中会连续不断的定时检查并释放闲置资源。当然为了实现这个过程,AutoReleasePool对所释放的类是有要求的,释放的类必须实现release()接口。下面通过一个例子来说明问题:

#include"Poco/AutoReleasePool.h"

using Poco::AutoReleasePool;

class C

{

public:

              C()

              {}

              void release()

              {

                            delete this;

              }

};

 

int main(int argc, char**argv)

{

              AutoReleasePool pool;

              C* pC = new C;

              pool.add(pC);

              pC = new C;

              pool.add(pC);

              return 0;

}

// all C's deleted

其类图如下:

                  

 在图中可以看出,AutoReleasePool实际上就是原生指针的一个容器,在其内部定义为:std::list ObjectList _list

要注意的是,如果同时使用AutoReleasePool和AutoPtr对指针进行管理时,应该如此实现:

                   AutoReleasePoolarp;

AutoPtr ptr = new C;

 ...

arp.add(ptr.duplicate());

很明显此刻AutoReleasePool和AutoPtr对对象应该共享所有权。

5. 动态工厂模板(DynamicFactoryClass Template)

       Poco中实现了一个动态工厂的模板,支持通过类名来创建类。其实现技术和前面的文章"FoundationSharedLibrary模块分析"中介绍的类似。

       动态工厂类DynamicFactory是抽象工厂类AbstractFactory的容器。

template

class DynamicFactory

              /// A factory that creates objects by class name.

{

          ....

          std::map FactoryMap _map;

}

AbstractFactory其实是模板类AbstractInstantiator的代称,是典型的工厂模式,用来实现具体类的创建,每个类工厂AbstractFactory对应一个类的创建。

template

class DynamicFactory

              /// A factory that creates objects by class name.

{

public:

              typedef AbstractInstantiatorAbstractFactory;

         ....

}

 而一个动态工厂DynamicFactory则可负责从一个基类继承的一组继承类的创建工作。如果有多个基类,用户必须创建多个动态工厂DynamicFactory去实现类的创建。

 

      同样为了完成通过类名来创建类的工作,DynamicFactory必须对类工厂进行注册,以便使编译器在编译时,建立类名和类的对应关系。

void registerClass(conststd::string& className)

              /// Registers the instantiator for the given class withthe DynamicFactory.

              /// The DynamicFactory takes ownership of theinstantiator and deletes

              /// it when it's no longer used.

              /// If the class has already been registered, anExistsException is thrown

              /// and the instantiator is deleted.

{

              registerClass(className, new Instantiator);

}

             

void registerClass(conststd::string& className, AbstractFactory* pAbstractFactory)

              /// Registers the instantiator for the given class withthe DynamicFactory.

              /// The DynamicFactory takes ownership of theinstantiator and deletes

              /// it when it's no longer used.

              /// If the class has already been registered, anExistsException is thrown

              /// and the instantiator is deleted.

{

              poco_check_ptr (pAbstractFactory);

 

              FastMutex::ScopedLock lock(_mutex);

 

              std::auto_ptrptr(pAbstractFactory);

              typename FactoryMap::iterator it =_map.find(className);

              if (it == _map.end())

                            _map[className] = ptr.release();

              else

                            throw ExistsException(className);

}

接下来再来看DynamicFactory类中的创建工作:

template

class DynamicFactory

              /// A factory that creates objects by class name.

{

              Base* createInstance(const std::string& className)const

                            /// Creates a new instance of the classwith the given name.

                            /// The class must have been registeredwith registerClass.

                            /// If the class name is unknown, aNotFoundException is thrown.

              {

                            FastMutex::ScopedLock lock(_mutex);

 

                            typename std::map::const_iterator it = _map.find(className);

                            if (it != _map.end())

                                          returnit->second->createInstance();

                            else

                                          throwNotFoundException(className);

              }

}

DynamicFactory类在找个合适的类工厂后,就把任务交给了类AbstractFactory去完成。


         下面是使用动态类工厂的一个例子:

#include"Poco/DynamicFactory.h"

#include"Poco/SharedPtr.h"

using Poco::DynamicFactory;

using Poco::SharedPtr;

class Base

{

};

class A: public Base

{

};

class B: public Base

{

};

 

int main(int argc, char**argv)

{

              DynamicFactory factory;

              factory.registerClass("A"); //creates Instantiator

              factory.registerClass("B"); //creates Instantiator

              SharedPtr pA =factory.createInstance("A");

              SharedPtr pB =factory.createInstance("B");

              // you can unregister classes

              factory.unregisterClass("B");

              // you can also check for the existence of a class

              bool haveA = factory.isClass("A"); // true

              bool haveB = factory.isClass("B"); // false(unregistered)

              bool haveC = factory.isClass("C"); // false(never registered)

              return 0;

}

由于在Poco中用Poco::Instantiator类创建对象时使用的是类对象的默认构造函数,所以对于类创建时期望不使用默认构造函数或者对构造函数有一些特殊初始化过程要求的情况,用户必须自己实现抽象构造工厂。下面是其一个例子:

#include"Poco/DynamicFactory.h"

using Poco::DynamicFactory;

usingPoco::AbstractInstantiator;

class Base

{

};

class A: public Base

{

};

class C: public Base

{

public:

              C(int i): _i(i)

              {}

private:

              int _i;

};

 

class CInstantiator: publicAbstractInstantiator

{

public:

              CInstantiator(int i): _i(i)

              {}

              Base* createInstance() const

              {

                            return new C(_i);

              }

private:

              int _i;

};

 

int main(int argc, char**argv)

{

              DynamicFactory factory;

              factory.registerClass("A");

              factory.registerClass("C", newCInstantiator(42));

              return 0;

}

  最后给出 AbstractFactory模块的类图:

6. 内存池(Memory Pools)

        同以往看过的内存池比较,Poco中内存池相当简单。既不支持对象的分配,也不对内存块大小进行分级,并且释放后的内存的合并策略也很简单。但这绝不是说它简陋,对于大多数情况,我觉得其完全够用了。同AutoReleasePool比较,两者的不同之处在于,AutoReleasePool中内存的分配是交由用户进行的,AutoReleasePool只负责释放,而MemoryPool的思想是,内存的分配和释放都由其管理。

       首先来回顾一下内存池的作用:

       1. 解决应用程序频繁申请和释放内存带来的执行效率问题

       2. 解决内存碎片问题      

 

       下面是Poco中内存池函数调用的一些特性:

       1. Poco::MemoryPool使用std::vector维护了一组固定大小的内存块指针,每个内存块大小都相等

       2. 可以通过MemoryPool::get()获得一个内存块的指针,如果池中内存块不够时,一个新的内存块会被分配。但当池中内存块数目到达池定义的上限时,一个OutOfMemoryException异常会被抛出。 

       3. 调用MemoryPool::release(void*ptr)将把内存块释放入池中

 

       其头文件中的定义如下:

class Foundation_APIMemoryPool

              /// A simple pool for fixed-size memory blocks.

              ///

              /// The main purpose of this class is to speed-up

              /// memory allocations, as well as to reduce memory

              /// fragmentation in situations where the same blocks

              /// are allocated all over again, such as in server

              /// applications.

              ///

              /// All allocated blocks are retained for future use.

              /// A limit on the number of blocks can be specified.

              /// Blocks can be preallocated.

{

public:

              MemoryPool(std::size_t blockSize, int preAlloc = 0, intmaxAlloc = 0);

                            /// Creates a MemoryPool for blocks withthe given blockSize.

                            /// The number of blocks given inpreAlloc are preallocated.

                           

              ~MemoryPool();

 

              void* get();

                            /// Returns a memory block. If there areno more blocks

                            /// in the pool, a new block will beallocated.

                            ///

                            /// If maxAlloc blocks are alreadyallocated, an

                            /// OutOfMemoryException is thrown.

                           

              void release(void* ptr);

                            /// Releases a memory block and returnsit to the pool.

             

              std::size_t blockSize() const;

                            /// Returns the block size.

                           

              int allocated() const;

                            /// Returns the number of allocatedblocks.

                           

              int available() const;

                            /// Returns the number of availableblocks in the pool.

 

private:

              MemoryPool();

              MemoryPool(const MemoryPool&);

              MemoryPool& operator = (const MemoryPool&);

             

              enum

              {

                            BLOCK_RESERVE = 128

              };

             

              typedef std::vector BlockVec;

             

              std::size_t _blockSize;

              int        _maxAlloc;

              int        _allocated;

              BlockVec    _blocks;

              FastMutex  _mutex;

};

其中_maxAlloc是内存池可分配的最大内存块数,_blockSize是每个内存块的大小。

 

      下面是内存池的一个例子:

#include"Poco/MemoryPool.h"

#include

#include

using Poco::MemoryPool;

int main(int argc, char**argv)

{

              MemoryPool pool(1024); // unlimited number of 1024 byteblocks

              // MemoryPool pool(1024, 4, 16); // at most 16 blocks;4 preallocated

              char* buffer =reinterpret_cast(pool.get());

              std::cin.read(buffer, pool.blockSize());

              std::streamsize n = std::cin.gcount();

              std::string s(buffer, n);

              pool.release(buffer);

              std::cout << s << std::endl;

              return 0;

}

最后给出MemoryPool的类图:

7. 单件(Singletons)

       Poco中的单件可以由类模板SingletonHolder完成。其定义如下:

template

class SingletonHolder

              /// This is a helper template class for managing

              /// singleton objects allocated on the heap.

              /// The class ensures proper deletion (including

              /// calling of the destructor) of singleton objects

              /// when the application that created them terminates.

{

public:

              SingletonHolder():

                            _pS(0)

                            /// Creates the SingletonHolder.

              {

              }

             

              ~SingletonHolder()

                            /// Destroys the SingletonHolder and thesingleton

                            /// object that it holds.

              {

                            delete _pS;

              }

             

              S* get()

                            /// Returns a pointer to the singletonobject

                            /// hold by the SingletonHolder. Thefirst call

                            /// to get will create the singleton.

              {

                            FastMutex::ScopedLock lock(_m);

                            if (!_pS) _pS = new S;

                            return _pS;

              }

             

private:

              S* _pS;

              FastMutex _m;

};

 一眼可以望穿的代码,实在没有什么可以说的。噢,补充一下。在Poco中的Singleton模型里并没有使用DCLP(Double Checked Locking)模式。什么是DCLP。可以参考文章Double Checked Locking 模式

      其类图如下:

 

POCO C++库学习和分析 -- 进程

 Poco::Foundation库中涉及进程的内容主要包括了4个主题,分别是进程(Process)、进程间同步(inter-processsynchronization)、管道(Pipes)、共享内存(Shared Memory)。我们都知道管道、共享内存、网络通讯是进程间数据交互的3种基本方式。由于网络通讯足够复杂,在Poco的结构划分里被单独分成了一个库Net,Foundation库中并没有涉及。下面一一介绍:

 

1. 进程

       关于中的进程其实没有什么可说的,不管是其内部实现还是外部使用都非常的简单。内部实现上只不过是不同操作系统进程API的封装,下面是它的类图:

 在Poco中进程类的所有成员函数都是静态函数。主要的功能函数覆盖3个方面:

     1. 创建新进程

     2. 销毁其他进程

     3. 获取当前进程信息

 

      值得注意的是,在Poco中进程创建时,可以对进程的I/O进程重定向。其函数如下:

ProcessHandle Process::launch( conststd::string& path, const std::vector& args, Pipe*inPipe, Pipe* outPipe, Pipe* errPipe)

2.  进程间同步

       Poco库中提供了Poco::NamedMutex和Poco::NamedEvent类用于进程间的同步。同线程间同步的类Mutex,Event相比,进程间同步都是命名的,这毫无疑问是因为操作系统的底层函数的要求。

       其类图如下:

3. 管道

       我们都知道管道是一个单向的通讯通道,或者用来读或者用来写。如果两个进程间要实现双向的通讯,必须在进程之间创建两个管道。Poco库中也封装了管道方便进程通讯,但Poco库中对于管道的读写,却不是通过管道的本身,而是通过Poco::PipeOutputStream和Poco::PipeInputStream 两个类。这样的话,便可以实现和标准库流操作的无缝结合。

       下面是一个例子来说明这几者的关系:

#include"Poco/Process.h"

#include"Poco/PipeStream.h"

#include"Poco/StreamCopier.h"

#include

using Poco::Process;

using Poco::ProcessHandle;

int main(int argc, char** argv)

{

              std::string cmd("/bin/ps");

              std::vector args;

              args.push_back("-ax");

              Poco::Pipe outPipe;

              ProcessHandle ph = Process::launch(cmd, args, 0,&outPipe, 0);

              Poco::PipeInputStream istr(outPipe);

              std::ofstream ostr("processes.txt");

              Poco::StreamCopier::copyStream(istr, ostr);

              return 0;

}

管道的类图如下:

4. 共享内存

       在Poco库中,Poco::SharedMemory类用于实现共享内存功能。它支持两种创建方式:

       1.从确定大小的内存区域

       2. 从文件(通过把文件映射入共享内存区域)

 

       而在接口上,Poco::SharedMemory只外露了两个接口:

char* begin() const;

char* end() const;

 begin()函数返回共享内存的起点,end()函数则返回其终点。下面是它的类图和两个使用例子,并不复杂:

例子一:

// Map a file into memory

#include"Poco/SharedMemory.h"

#include"Poco/File.h"

using Poco::SharedMemory;

using Poco::File;

int main(int argc, char**argv)

{

              File f("MapIntoMemory.dat");

              SharedMemory mem(f, SharedMemory::AM_READ); //read-only access

              for (char* ptr = mem.begin(); ptr != mem.end(); ++ptr)

              {

                            // ...

              }

              return 0;

}

例子二:

// Share a memory region of1024 bytes

#include"Poco/SharedMemory.h"

using Poco::SharedMemory;

int main(int argc, char**argv)

{

              SharedMemory mem("MySharedMemory", 1024,

                            SharedMemory::AM_READ |SharedMemory::AM_WRITE);

              for (char* ptr = mem.begin(); ptr != mem.end(); ++ptr)

              {

                            *ptr = 0;

              }

              return 0;

}

 

POCO C++库学习和分析 -- 通知和事件(一)

POCO C++库学习和分析 -- 通知和事件 (一)

 

1. 信息交流的方法

        在讨论Poco中事件与通知之前,先来聊一聊信息交流的方法,这样或许有助于理解接下去的讨论。我们都知道数据之间存在关系。在数据库模型里,关系被分为一对一,一对多,多对多。在用计算机去解决数据关系的时候,多对多关系往往被分解成为数个一对多,而一对多的关系最终被分解成为数个一对一关系。
       如果用关系的观点去看消息流动,消息存在一个或多个发起者,即消息源Source;消息也存在一个或多个接收者,即目标对象Target;同时消息Message本身具有内容,即多种消息。简化成为最终的一对一模型,来描述消息的,那么这个模型里的三个要素就是,Source,Message,Target。

       把这个模型放到C++语言中,Source,Message,Target分别被抽象成为三个类。那么消息传送的方式可以被写成如下两种方式:

 

1.1 放置模型于编程语言

       从消息源的角度来考虑问题,整个消息发送的流程是:
       a) 目标向消息源注册, Source.Register(Target)
       函数实现大致是这样的

[cpp] view plaincopy

1.   Source.Register(Target)  

2.   {  

3.        source.vec.add(Target);  

4.   }  

       b) 消息产生
       Source create a msg
       c) 消息发送,Soucre.Send(Msg)
       上面这个函数大致如下:

[cpp] view plaincopy

1.   Soucre.Send(Msg)  

2.   {  

3.       Target.Receive(Msg);  

4.   }  

       Target.Receive(Msg)的函数实现大致是这样的,

[cpp] view plaincopy

1.   Target.Receive(Msg)  

2.   {  

3.       switch(Msg)  

4.       {  

5.          case Msg1:  

6.            doing something;  

7.          case Msg2:  

8.            doing something;  

9.          default:  

10.           doing something;  

11.      }  

12.  }  

       这种方式的调用可以说是最常见能够想到的方法了。最早写C代码的时候,就开始使用了,到C++时代也是。

       换个角度,在C++时代一切都是对象,从消息的角度来考虑这个问题呢,于是整个消息发送流程变成为:
       a) 目标向消息注册, Msg.Register(Target);
       Msg.Register(Target)函数实现大致是这样的

[cpp] view plaincopy

1.   Msg.Register(Target)  

2.     

3.       Msg.vec.add(Target);  

       b) 消息产生
       a msg create by some one source 
       c) 消息发送,Msg.Send(Source)
       Msg.Send(Source)函数实现大致是这样的,

[cpp] view plaincopy

1.   Msg.Send(Source)  

2.   {  

3.       switch(Source)  

4.       {  

5.          case Source1:  

6.            doing something;  

7.          case Source2:  

8.            doing something;  

9.          default:  

10.           doing something;  

11.      }  

12.  }  


       上面就是Poco中通知与事件的大致思路。其中通知是站在消息源的角度来考虑问题,而事件是站在消息的角度来考虑问题。插一句话,Poco中的事件和代理来自于C#。也就是说,分析Poco中的事件,其实是在解释C#的代理和事件的实现。

1.2 放置模型于多线程环境

       让我们抛开语言吧,把消息传递的过程放到多线程当中去。用多线程干吗?消息的产生就是为了最终的处理,假如消息处理很耗时间怎么办?
       没办法,兜里没银子。那句话怎么说来着,高富帅猛升硬件,穷挫矮死搞算法。毕竟大家不全是铁道部,是不?于是乎把消息的产生和处理放在两个线程中不是挺好的一个主意吗,这样毫无疑问消息处理的效率得到了提升。这也就是生产者和消费者模式。
       如果从这个角度考虑的问题的话,那么我们得到了消息传递的另外一种划分。同步处理消息和异步处理消息。

       于是我们可以把消息的传递过程分为下面4种:


         
                    通知    |    事件
                               |
       同步    (支持)  |  (支持)  
             ———————————
       异步    (支持)  |  (支持)  
                              |

       下面的章节我们将对Poco中消息和事件一一进行分析。

 

(版权所有,转载时请注明作者和出处  http://blog.csdn.net/arau_sh/article/details/8664372

 

 POCO C++库学习和分析 -- 通知和事件 ( 二 )

2. 通知和事件的总览

2.1 相关类信息

        下面是Poco库和通知、事件相关的类
        1)  同步通知实现:类Notification和NotificationCenter
        2)  异步通知实现:类Notification和NotificationQueue
        3)  事件 Events


2.2 概述

        Poco文档上对于通知和事件的区别做了如下描述:
        1)  通知和事件是Poco库中支持的两种消息通知机制,目的是为了在源对象(source)发生某事件(something)时能够及时通知目的对象(target)
        2)  使用Poco中的通知,必须注意通知对象(target)也可称观察者(observer)将无法得知事件源的情况。Poco::NotificationCenter和Poco::NotificationQueue是消息传递的中间载体,用来对源(source)和目标(target)进行解耦。
        3) 如果对象(target)或者说观察者(observer)期望知道事件源的情况,或者想只从某一个确切的源接收事件,可以使用Poco::Event。Poco中的Event同时支持异步和同步消息。
        看了上面的文档,千万不要以为通知无法获取源对象的信息。事实上,通过对代码的改写,我们也可以使通知支持上述功能。只不过通知是基于消息源角度的设计,在设计时,就认为对于通知者,关注重点并不在消息源,而在消息类型。关于这一点,可以看前面一篇文章POCOC++库学习和分析 -- 通知和事件(一)

        下图说明了同步消息时,消息发送的流程:

       

3. 同步通知

3.1 消息

        所有的通知类都继承自Poco::Notification,其定义如下:

[cpp] view plaincopy

1.   class Notification: public RefCountedObject  

2.   {  

3.   public:  

4.       typedef AutoPtr Ptr;  

5.       Notification();  

6.       virtual std::string name() const;  

7.     

8.     

9.   protected:  

10.      virtual ~Notification();  

11.  };  


        从定义看,我们可以发现其从RefCountedObject类继承,也就是说其是一个引用计数对象。作为从RefCountedObject中继承的引用计数对象,毫无疑问在其在Poco中使用是和AutoPtr类配合的,完成堆对象的自动回收。关于AutoPtr的介绍,可以看前面的文章POCOC++库学习和分析 -- 内存管理()

        使用时,我们可以从Notification继承,以便实现自己的通知,并且在通知类中可以放置我们想的任意数据,但是Poco中的Notification继承类不支持值语义操作,也就是说不支持拷贝构造函数(copy constructor)和赋值构造函数(assignment)。要注意的是这个限制并不是由于Notification继承类本身的限制导致的不支持,我们完全可以为其实现拷贝构造函数和赋值构造函数。这个限制是使用继承类的时候,为了实现动态对象的自动回收,消息的中介Poco::NotificationCenter和接收者Observer都使用了Poco::AutoPtr去传递和接收数据造成的。所以所有的Notification继承类对象都在堆上分配,运用时没有必要为其提供拷贝构造函数和赋值构造函数。

 



3.2 消息的发送者 source

        类NotificationCenter类扮演了一个消息源的角色。下面是它的定义:

[cpp] view plaincopy

1.   class NotificationCenter  

2.   {  

3.   public:  

4.       NotificationCenter();  

5.       ~NotificationCenter();  

6.     

7.       void addObserver(const AbstractObserver& observer);  

8.       void removeObserver(const AbstractObserver& observer);  

9.     

10.      void postNotification(Notification::Ptr pNotification);  

11.    

12.      bool hasObservers() const;    

13.      std::size_t countObservers() const;  

14.            

15.      static NotificationCenter& defaultCenter();  

16.    

17.  private:  

18.      typedef SharedPtr AbstractObserverPtr;  

19.      typedef std::vector ObserverList;  

20.    

21.      ObserverList  _observers;  

22.      mutable Mutex _mutex;  

23.  };  


        从定义可以看出它是一个目标对象的集合std::vector>_observers。
        通过调用函数addObserver(constAbstractObserver& observer),可以完成目标对象的注册过程。调用函数removeObserver()则可以完成反注册。而函数postNotification是一个消息传递的过程,其定义如下:

[cpp] view plaincopy

1.   void NotificationCenter::postNotification(Notification::Ptr pNotification)  

2.   {  

3.       poco_check_ptr (pNotification);  

4.     

5.     

6.       ScopedLockWithUnlock lock(_mutex);  

7.       ObserverList observersToNotify(_observers);  

8.       lock.unlock();  

9.       for (ObserverList::iterator it = observersToNotify.begin(); it !=   

10.    

11.    

12.  observersToNotify.end(); ++it)  

13.      {  

14.          (*it)->notify(pNotification);  

15.      }  

16.  }  


        从它的实现看,只是简单遍历_observers对象,并最终通过AbstractObserver->notify()把消息发送给通知对象。同时为了避免长时间占用_observers对象,在发送消息时,复制了一份。

        当使用者调用postNotification函数时,毫无疑问,消息被触发。

 

3.3 消息的接收者 target

        消息产生后,最终都要求被发送给合适的处理者。在C++中,处理者一定是一个对象,而处理即意味着行为,在C++中意味着类的成员函数,也就是说最终的处理要落实到类的对象实例的函数指针上。在Poco中,AbstractObserver可以理解成对象类的一个代理,它是一个纯虚类,定义了接收对象的接口。它的定义如下:

[cpp] view plaincopy

1.   class AbstractObserver  

2.   {  

3.   public:  

4.       AbstractObserver();  

5.       AbstractObserver(const AbstractObserver& observer);  

6.       virtual ~AbstractObserver();  

7.         

8.       AbstractObserver& operator = (const AbstractObserver& observer);  

9.     

10.    

11.      virtual void notify(Notification* pNf) const = 0;  

12.      virtual bool equals(const AbstractObserver& observer) const = 0;  

13.      virtual bool accepts(Notification* pNf) const = 0;  

14.      virtual AbstractObserver* clone() const = 0;  

15.      virtual void disable() = 0;  

16.  };  


        所有的接收者代理类都从类AbstractObserver继承,因为真实接收者的类类型未定,所以接受者代理类只能由模板技术去实现。这解决了处理时第一个问题。第二个问题是,不同的类处理函数可以不同。可以拥有一个,或多个参数,可以拥有或没有返回值。而编译器编译时,必须指定函数指针的类型。解决方法即把处理函数的类型固定下来。
        在Poco库的内部实现Observer类和NObserver类中,其定义分别是:

[cpp] view plaincopy

1.   void (C::*Callback)(N*);  

2.   void (C::*Callback)(const AutoPtr &);  

        函数调用时分别带了一个参数。这其实解决了所有的问题,因为一个参数可以是结构体,供使用传入传出所需的值。当然我们也可以从AbstractObserver继承实现自己的目标代理类,这样我们可以定义自己所需要的函数类型。让我们来看一下Observer定义,NObserver的分析类似。

[cpp] view plaincopy

1.   template <class C, class N>  

2.   class Observer: public AbstractObserver  

3.   {  

4.   public:  

5.       typedef void (C::*Callback)(N*);  

6.     

7.     

8.       Observer(C& object, Callback method):   

9.           _pObject(&object),   

10.          _method(method)  

11.      {  

12.      }  

13.        

14.      Observer(const Observer& observer):  

15.          AbstractObserver(observer),  

16.          _pObject(observer._pObject),   

17.          _method(observer._method)  

18.      {  

19.      }  

20.        

21.      ~Observer()  

22.      {  

23.      }  

24.        

25.      Observer& operator = (const Observer& observer)  

26.      {  

27.          if (&observer != this)  

28.          {  

29.              _pObject = observer._pObject;  

30.              _method  = observer._method;  

31.          }  

32.          return *this;  

33.      }  

34.        

35.      void notify(Notification* pNf) const  

36.      {  

37.          Poco::Mutex::ScopedLock lock(_mutex);  

38.    

39.    

40.          if (_pObject)  

41.          {  

42.              N* pCastNf = dynamic_cast(pNf);  

43.              if (pCastNf)  

44.              {  

45.                  pCastNf->duplicate();  

46.                  (_pObject->*_method)(pCastNf);  

47.              }  

48.          }  

49.      }  

50.        

51.      bool equals(const AbstractObserver& abstractObserver) const  

52.      {  

53.          const Observer* pObs = dynamic_cast<const Observer*>(&abstractObserver);  

54.          return pObs && pObs->_pObject == _pObject && pObs->_method == _method;  

55.      }  

56.    

57.    

58.      bool accepts(Notification* pNf) const  

59.      {  

60.          return dynamic_cast(pNf) != 0;  

61.      }  

62.        

63.      AbstractObserver* clone() const  

64.      {  

65.          return new Observer(*this);  

66.      }  

67.        

68.      void disable()  

69.      {  

70.          Poco::Mutex::ScopedLock lock(_mutex);  

71.            

72.          _pObject = 0;  

73.      }  

74.        

75.  private:  

76.      Observer();  

77.    

78.    

79.      C*       _pObject;  

80.      Callback _method;  

81.      mutable Poco::Mutex _mutex;  

82.  };  

        Observer中存在一个类实例对象的指针_pObject,以及对应函数入口地址_method。其处理函数为notify。这里注意两点:
        1. 使用了dynamic_cast转换,这意味着接受者处理的消息是向下继承的。如果一个对象订购了Poco::Notification,那么它将接受到所有继承自Poco::Notification的消息。

       2. 调用了pCastNf->duplicate(),增加了引用计数,这意味着处理者在处理函数中必须相应的去调用pCastNf->release(),去释放引用计数。在这里我倒是没有搞明白,为什么要调用duplicate(),在我看来不调用也完全可以。可能是为了照顾引用计数对象的语义,即引用计数对象的所有权发生了改变,从NotificationCenter对象独占转变成为了真实处理类对象和NotificationCenter对象共同拥有所有权。

 

3.4 一个使用例子

[cpp] view plaincopy

1.   #include "Poco/NotificationCenter.h"  

2.   #include "Poco/Notification.h"  

3.   #include "Poco/Observer.h"  

4.   #include "Poco/NObserver.h"  

5.   #include "Poco/AutoPtr.h"  

6.   #include   

7.   using Poco::NotificationCenter;  

8.   using Poco::Notification;  

9.   using Poco::Observer;  

10.  using Poco::NObserver;  

11.  using Poco::AutoPtr;  

12.  class BaseNotification: public Notification  

13.  {  

14.  };  

15.  class SubNotification: public BaseNotification  

16.  {  

17.  };  

18.    

19.    

20.  class Target  

21.  {  

22.  public:  

23.      void handleBase(BaseNotification* pNf)  

24.      {  

25.          std::cout << "handleBase: " << pNf->name() << std::endl;  

26.          pNf->release(); // we got ownership, so we must release  

27.      }  

28.      void handleSub(const AutoPtr& pNf)  

29.      {  

30.          std::cout << "handleSub: " << pNf->name() << std::endl;  

31.      }  

32.  };  

33.    

34.    

35.  int main(int argc, char** argv)  

36.  {  

37.      NotificationCenter nc;  

38.      Target target;  

39.      nc.addObserver(  

40.          Observer(target, &Target::handleBase)  

41.          );  

42.      nc.addObserver(  

43.          NObserver(target, &Target::handleSub)  

44.          );  

45.      nc.postNotification(new BaseNotification);  

46.      nc.postNotification(new SubNotification);  

47.      nc.removeObserver(  

48.          Observer(target, &Target::handleBase)  

49.          );  

50.      nc.removeObserver(  

51.          NObserver(target, &Target::handleSub)  

52.          );  

53.      return 0;  

54.  }  



3.5 最后给出同步通知的类图


POCO C++库学习和分析 -- 通知和事件 ( 二 )

2. 通知和事件的总览

2.1 相关类信息

        下面是Poco库和通知、事件相关的类
        1)  同步通知实现:类Notification和NotificationCenter
        2)  异步通知实现:类Notification和NotificationQueue
        3)  事件 Events


2.2 概述

        Poco文档上对于通知和事件的区别做了如下描述:
        1)  通知和事件是Poco库中支持的两种消息通知机制,目的是为了在源对象(source)发生某事件(something)时能够及时通知目的对象(target)
        2)  使用Poco中的通知,必须注意通知对象(target)也可称观察者(observer)将无法得知事件源的情况。Poco::NotificationCenter和Poco::NotificationQueue是消息传递的中间载体,用来对源(source)和目标(target)进行解耦。
        3) 如果对象(target)或者说观察者(observer)期望知道事件源的情况,或者想只从某一个确切的源接收事件,可以使用Poco::Event。Poco中的Event同时支持异步和同步消息。
        看了上面的文档,千万不要以为通知无法获取源对象的信息。事实上,通过对代码的改写,我们也可以使通知支持上述功能。只不过通知是基于消息源角度的设计,在设计时,就认为对于通知者,关注重点并不在消息源,而在消息类型。关于这一点,可以看前面一篇文章POCOC++库学习和分析 -- 通知和事件(一)

        下图说明了同步消息时,消息发送的流程:

       

3. 同步通知

3.1 消息

        所有的通知类都继承自Poco::Notification,其定义如下:

[cpp] view plaincopy

1.   class Notification: public RefCountedObject  

2.   {  

3.   public:  

4.       typedef AutoPtr Ptr;  

5.       Notification();  

6.       virtual std::string name() const;  

7.     

8.     

9.   protected:  

10.      virtual ~Notification();  

11.  };  


        从定义看,我们可以发现其从RefCountedObject类继承,也就是说其是一个引用计数对象。作为从RefCountedObject中继承的引用计数对象,毫无疑问在其在Poco中使用是和AutoPtr类配合的,完成堆对象的自动回收。关于AutoPtr的介绍,可以看前面的文章POCOC++库学习和分析 -- 内存管理()

        使用时,我们可以从Notification继承,以便实现自己的通知,并且在通知类中可以放置我们想的任意数据,但是Poco中的Notification继承类不支持值语义操作,也就是说不支持拷贝构造函数(copy constructor)和赋值构造函数(assignment)。要注意的是这个限制并不是由于Notification继承类本身的限制导致的不支持,我们完全可以为其实现拷贝构造函数和赋值构造函数。这个限制是使用继承类的时候,为了实现动态对象的自动回收,消息的中介Poco::NotificationCenter和接收者Observer都使用了Poco::AutoPtr去传递和接收数据造成的。所以所有的Notification继承类对象都在堆上分配,运用时没有必要为其提供拷贝构造函数和赋值构造函数。

 



3.2 消息的发送者 source

        类NotificationCenter类扮演了一个消息源的角色。下面是它的定义:

[cpp] view plaincopy

1.   class NotificationCenter  

2.   {  

3.   public:  

4.       NotificationCenter();  

5.       ~NotificationCenter();  

6.     

7.       void addObserver(const AbstractObserver& observer);  

8.       void removeObserver(const AbstractObserver& observer);  

9.     

10.      void postNotification(Notification::Ptr pNotification);  

11.    

12.      bool hasObservers() const;    

13.      std::size_t countObservers() const;  

14.            

15.      static NotificationCenter& defaultCenter();  

16.    

17.  private:  

18.      typedef SharedPtr AbstractObserverPtr;  

19.      typedef std::vector ObserverList;  

20.    

21.      ObserverList  _observers;  

22.      mutable Mutex _mutex;  

23.  };  


        从定义可以看出它是一个目标对象的集合std::vector>_observers。
        通过调用函数addObserver(constAbstractObserver& observer),可以完成目标对象的注册过程。调用函数removeObserver()则可以完成反注册。而函数postNotification是一个消息传递的过程,其定义如下:

[cpp] view plaincopy

1.   void NotificationCenter::postNotification(Notification::Ptr pNotification)  

2.   {  

3.       poco_check_ptr (pNotification);  

4.     

5.     

6.       ScopedLockWithUnlock lock(_mutex);  

7.       ObserverList observersToNotify(_observers);  

8.       lock.unlock();  

9.       for (ObserverList::iterator it = observersToNotify.begin(); it !=   

10.    

11.    

12.  observersToNotify.end(); ++it)  

13.      {  

14.          (*it)->notify(pNotification);  

15.      }  

16.  }  


        从它的实现看,只是简单遍历_observers对象,并最终通过AbstractObserver->notify()把消息发送给通知对象。同时为了避免长时间占用_observers对象,在发送消息时,复制了一份。

        当使用者调用postNotification函数时,毫无疑问,消息被触发。

 

3.3 消息的接收者 target

        消息产生后,最终都要求被发送给合适的处理者。在C++中,处理者一定是一个对象,而处理即意味着行为,在C++中意味着类的成员函数,也就是说最终的处理要落实到类的对象实例的函数指针上。在Poco中,AbstractObserver可以理解成对象类的一个代理,它是一个纯虚类,定义了接收对象的接口。它的定义如下:

[cpp] view plaincopy

1.   class AbstractObserver  

2.   {  

3.   public:  

4.       AbstractObserver();  

5.       AbstractObserver(const AbstractObserver& observer);  

6.       virtual ~AbstractObserver();  

7.         

8.       AbstractObserver& operator = (const AbstractObserver& observer);  

9.     

10.    

11.      virtual void notify(Notification* pNf) const = 0;  

12.      virtual bool equals(const AbstractObserver& observer) const = 0;  

13.      virtual bool accepts(Notification* pNf) const = 0;  

14.      virtual AbstractObserver* clone() const = 0;  

15.      virtual void disable() = 0;  

16.  };  


        所有的接收者代理类都从类AbstractObserver继承,因为真实接收者的类类型未定,所以接受者代理类只能由模板技术去实现。这解决了处理时第一个问题。第二个问题是,不同的类处理函数可以不同。可以拥有一个,或多个参数,可以拥有或没有返回值。而编译器编译时,必须指定函数指针的类型。解决方法即把处理函数的类型固定下来。
        在Poco库的内部实现Observer类和NObserver类中,其定义分别是:

[cpp] view plaincopy

1.   void (C::*Callback)(N*);  

2.   void (C::*Callback)(const AutoPtr &);  

        函数调用时分别带了一个参数。这其实解决了所有的问题,因为一个参数可以是结构体,供使用传入传出所需的值。当然我们也可以从AbstractObserver继承实现自己的目标代理类,这样我们可以定义自己所需要的函数类型。让我们来看一下Observer定义,NObserver的分析类似。

[cpp] view plaincopy

1.   template <class C, class N>  

2.   class Observer: public AbstractObserver  

3.   {  

4.   public:  

5.       typedef void (C::*Callback)(N*);  

6.     

7.     

8.       Observer(C& object, Callback method):   

9.           _pObject(&object),   

10.          _method(method)  

11.      {  

12.      }  

13.        

14.      Observer(const Observer& observer):  

15.          AbstractObserver(observer),  

16.          _pObject(observer._pObject),   

17.          _method(observer._method)  

18.      {  

19.      }  

20.        

21.      ~Observer()  

22.      {  

23.      }  

24.        

25.      Observer& operator = (const Observer& observer)  

26.      {  

27.          if (&observer != this)  

28.          {  

29.              _pObject = observer._pObject;  

30.              _method  = observer._method;  

31.          }  

32.          return *this;  

33.      }  

34.        

35.      void notify(Notification* pNf) const  

36.      {  

37.          Poco::Mutex::ScopedLock lock(_mutex);  

38.    

39.    

40.          if (_pObject)  

41.          {  

42.              N* pCastNf = dynamic_cast(pNf);  

43.              if (pCastNf)  

44.              {  

45.                  pCastNf->duplicate();  

46.                  (_pObject->*_method)(pCastNf);  

47.              }  

48.          }  

49.      }  

50.        

51.      bool equals(const AbstractObserver& abstractObserver) const  

52.      {  

53.          const Observer* pObs = dynamic_cast<const Observer*>(&abstractObserver);  

54.          return pObs && pObs->_pObject == _pObject && pObs->_method == _method;  

55.      }  

56.    

57.    

58.      bool accepts(Notification* pNf) const  

59.      {  

60.          return dynamic_cast(pNf) != 0;  

61.      }  

62.        

63.      AbstractObserver* clone() const  

64.      {  

65.          return new Observer(*this);  

66.      }  

67.        

68.      void disable()  

69.      {  

70.          Poco::Mutex::ScopedLock lock(_mutex);  

71.            

72.          _pObject = 0;  

73.      }  

74.        

75.  private:  

76.      Observer();  

77.    

78.    

79.      C*       _pObject;  

80.      Callback _method;  

81.      mutable Poco::Mutex _mutex;  

82.  };  

        Observer中存在一个类实例对象的指针_pObject,以及对应函数入口地址_method。其处理函数为notify。这里注意两点:
        1. 使用了dynamic_cast转换,这意味着接受者处理的消息是向下继承的。如果一个对象订购了Poco::Notification,那么它将接受到所有继承自Poco::Notification的消息。

       2. 调用了pCastNf->duplicate(),增加了引用计数,这意味着处理者在处理函数中必须相应的去调用pCastNf->release(),去释放引用计数。在这里我倒是没有搞明白,为什么要调用duplicate(),在我看来不调用也完全可以。可能是为了照顾引用计数对象的语义,即引用计数对象的所有权发生了改变,从NotificationCenter对象独占转变成为了真实处理类对象和NotificationCenter对象共同拥有所有权。

 

3.4 一个使用例子

[cpp] view plaincopy

1.   #include "Poco/NotificationCenter.h"  

2.   #include "Poco/Notification.h"  

3.   #include "Poco/Observer.h"  

4.   #include "Poco/NObserver.h"  

5.   #include "Poco/AutoPtr.h"  

6.   #include   

7.   using Poco::NotificationCenter;  

8.   using Poco::Notification;  

9.   using Poco::Observer;  

10.  using Poco::NObserver;  

11.  using Poco::AutoPtr;  

12.  class BaseNotification: public Notification  

13.  {  

14.  };  

15.  class SubNotification: public BaseNotification  

16.  {  

17.  };  

18.    

19.    

20.  class Target  

21.  {  

22.  public:  

23.      void handleBase(BaseNotification* pNf)  

24.      {  

25.          std::cout << "handleBase: " << pNf->name() << std::endl;  

26.          pNf->release(); // we got ownership, so we must release  

27.      }  

28.      void handleSub(const AutoPtr& pNf)  

29.      {  

30.          std::cout << "handleSub: " << pNf->name() << std::endl;  

31.      }  

32.  };  

33.    

34.    

35.  int main(int argc, char** argv)  

36.  {  

37.      NotificationCenter nc;  

38.      Target target;  

39.      nc.addObserver(  

40.          Observer(target, &Target::handleBase)  

41.          );  

42.      nc.addObserver(  

43.          NObserver(target, &Target::handleSub)  

44.          );  

45.      nc.postNotification(new BaseNotification);  

46.      nc.postNotification(new SubNotification);  

47.      nc.removeObserver(  

48.          Observer(target, &Target::handleBase)  

49.          );  

50.      nc.removeObserver(  

51.          NObserver(target, &Target::handleSub)  

52.          );  

53.      return 0;  

54.  }  



3.5 最后给出同步通知的类图

 POCO C++库学习和分析 -- 通知和事件 (三)

4. 异步通知

4.1 NotificationQueue类

         Poco中的异步通知是通过NotificationQueue类来实现的,同它功能类似还有类PriorityNotificationQueue和TimedNotificationQueue。不同的是PriorityNotificationQueue类中对消息分了优先级,对优先级高的消息优先处理;而TimedNotificationQueue对消息给了时间戳,时间戳早的优先处理,而和其压入队列的时间无关。所以接下来我们主要关注NotificationQueue的实现。
        事实上NotificationQueue是个非常有趣的类。让我们来看一下它的头文件:

[cpp] view plaincopy

1.   class Foundation_API NotificationQueue  

2.       /// A NotificationQueue object provides a way to implement asynchronous  

3.       /// notifications. This is especially useful for sending notifications  

4.       /// from one thread to another, for example from a background thread to   

5.       /// the main (user interface) thread.   

6.       ///   

7.       /// The NotificationQueue can also be used to distribute work from  

8.       /// a controlling thread to one or more worker threads. Each worker thread  

9.       /// repeatedly calls waitDequeueNotification() and processes the  

10.      /// returned notification. Special care must be taken when shutting  

11.      /// down a queue with worker threads waiting for notifications.  

12.      /// The recommended sequence to shut down and destroy the queue is to  

13.      ///   1. set a termination flag for every worker thread  

14.      ///   2. call the wakeUpAll() method  

15.      ///   3. join each worker thread  

16.      ///   4. destroy the notification queue.  

17.  {  

18.  public:  

19.      NotificationQueue();  

20.          /// Creates the NotificationQueue.  

21.    

22.      ~NotificationQueue();  

23.          /// Destroys the NotificationQueue.  

24.    

25.      void enqueueNotification(Notification::Ptr pNotification);  

26.          /// Enqueues the given notification by adding it to  

27.          /// the end of the queue (FIFO).  

28.          /// The queue takes ownership of the notification, thus  

29.          /// a call like  

30.          ///     notificationQueue.enqueueNotification(new MyNotification);  

31.          /// does not result in a memory leak.  

32.            

33.      void enqueueUrgentNotification(Notification::Ptr pNotification);  

34.          /// Enqueues the given notification by adding it to  

35.          /// the front of the queue (LIFO). The event therefore gets processed  

36.          /// before all other events already in the queue.  

37.          /// The queue takes ownership of the notification, thus  

38.          /// a call like  

39.          ///     notificationQueue.enqueueUrgentNotification(new MyNotification);  

40.          /// does not result in a memory leak.  

41.    

42.      Notification* dequeueNotification();  

43.          /// Dequeues the next pending notification.  

44.          /// Returns 0 (null) if no notification is available.  

45.          /// The caller gains ownership of the notification and  

46.          /// is expected to release it when done with it.  

47.          ///  

48.          /// It is highly recommended that the result is immediately  

49.          /// assigned to a Notification::Ptr, to avoid potential  

50.          /// memory management issues.  

51.    

52.      Notification* waitDequeueNotification();  

53.          /// Dequeues the next pending notification.  

54.          /// If no notification is available, waits for a notification  

55.          /// to be enqueued.   

56.          /// The caller gains ownership of the notification and  

57.          /// is expected to release it when done with it.  

58.          /// This method returns 0 (null) if wakeUpWaitingThreads()  

59.          /// has been called by another thread.  

60.          ///  

61.          /// It is highly recommended that the result is immediately  

62.          /// assigned to a Notification::Ptr, to avoid potential  

63.          /// memory management issues.  

64.    

65.      Notification* waitDequeueNotification(long milliseconds);  

66.          /// Dequeues the next pending notification.  

67.          /// If no notification is available, waits for a notification  

68.          /// to be enqueued up to the specified time.  

69.          /// Returns 0 (null) if no notification is available.  

70.          /// The caller gains ownership of the notification and  

71.          /// is expected to release it when done with it.  

72.          ///  

73.          /// It is highly recommended that the result is immediately  

74.          /// assigned to a Notification::Ptr, to avoid potential  

75.          /// memory management issues.  

76.    

77.      void dispatch(NotificationCenter& notificationCenter);  

78.          /// Dispatches all queued notifications to the given  

79.          /// notification center.  

80.    

81.      void wakeUpAll();  

82.          /// Wakes up all threads that wait for a notification.  

83.        

84.      bool empty() const;  

85.          /// Returns true iff the queue is empty.  

86.            

87.      int size() const;  

88.          /// Returns the number of notifications in the queue.  

89.    

90.      void clear();  

91.          /// Removes all notifications from the queue.  

92.            

93.      bool hasIdleThreads() const;      

94.          /// Returns true if the queue has at least one thread waiting   

95.          /// for a notification.  

96.            

97.      static NotificationQueue& defaultQueue();  

98.          /// Returns a reference to the default  

99.          /// NotificationQueue.  

100.   

101. protected:  

102.     Notification::Ptr dequeueOne();  

103.       

104. private:  

105.     typedef std::deque NfQueue;  

106.     struct WaitInfo  

107.     {  

108.         Notification::Ptr pNf;  

109.         Event             nfAvailable;  

110.     };  

111.     typedef std::deque WaitQueue;  

112.   

113.     NfQueue           _nfQueue;  

114.     WaitQueue         _waitQueue;  

115.     mutable FastMutex _mutex;  

116. };  


        从定义可以看到NotificationQueue类管理了两个deque容器。其中一个是WaitInfo对象的deque,另一个是Notification对象的deque。而WaitInfo一对一的对应了一个消息对象pNf和事件对象nfAvailable,毫无疑问Event对象是用来同步多线程的。让我们来看看它如何实现。
       NotificationQueue实现的巧妙之处就在于WaitInfo由消费者动态创建,消费者线程通过函数Notification* waitDequeueNotification()获取消息,其函数定义如下:

[cpp] view plaincopy

1.   Notification* NotificationQueue::waitDequeueNotification()  

2.   {  

3.       Notification::Ptr pNf;  

4.       WaitInfo* pWI = 0;  

5.       {  

6.           FastMutex::ScopedLock lock(_mutex);  

7.           pNf = dequeueOne();  

8.           if (pNf) return pNf.duplicate();  

9.           pWI = new WaitInfo;  

10.          _waitQueue.push_back(pWI);  

11.      }  

12.      pWI->nfAvailable.wait();  

13.      pNf = pWI->pNf;  

14.      delete pWI;  

15.      return pNf.duplicate();  

16.  }  

17.    

18.  Notification::Ptr NotificationQueue::dequeueOne()  

19.  {  

20.      Notification::Ptr pNf;  

21.      if (!_nfQueue.empty())  

22.      {  

23.          pNf = _nfQueue.front();  

24.          _nfQueue.pop_front();  

25.      }  

26.      return pNf;  

27.  }  


        消费者线程首先从Notification对象的deque中获取消息,如果消息获取不为空,则直接返回处理,如果消息为空,则创建一个新的WaitInfo对象,并压入WaitInfo对象的
deque。消费者线程开始等待,直到生产者通知有消息的存在,然后再从WaitInfo对象中取出消息,返回处理。当消费者线程能从Notification对象的deque中获取到消息时,说明消费者处理消息的速度要比生成者低;反之则说明消费者处理消息的速度要比生成者高。

        让我们再看一下生产者的调用函数voidNotificationQueue::enqueueNotification(Notification::Ptr pNotification),其定义如下:

[cpp] view plaincopy

1.   void NotificationQueue::enqueueNotification(Notification::Ptr pNotification)  

2.   {  

3.       poco_check_ptr (pNotification);  

4.       FastMutex::ScopedLock lock(_mutex);  

5.       if (_waitQueue.empty())  

6.       {  

7.           _nfQueue.push_back(pNotification);  

8.       }  

9.       else  

10.      {  

11.          WaitInfo* pWI = _waitQueue.front();  

12.          _waitQueue.pop_front();  

13.          pWI->pNf = pNotification;  

14.          pWI->nfAvailable.set();  

15.      }     

16.  }  


        生产者线程首先判断WaitInfo对象的deque是否为空,如果不为空,说明存在消费者线程等待,则从deque中获取一个WaitInfo对象,灌入Notification消息,释放信号量激活消费者线程;而如果为空,说明目前说有的消费者线程都在工作,则把消息暂时存入Notification对象的deque,等待消费者线程有空时处理。
        整个处理过程中对于_mutex对象的处理是非常小心的,_waitQueue不被使用则释放。OK,整个流程结束,消息源和目标被解耦。


4.2 一个异步通知的例子

[cpp] view plaincopy

1.   #include "Poco/Notification.h"  

2.   #include "Poco/NotificationQueue.h"  

3.   #include "Poco/ThreadPool.h"  

4.   #include "Poco/Runnable.h"  

5.   #include "Poco/AutoPtr.h"  

6.   using Poco::Notification;  

7.   using Poco::NotificationQueue;  

8.   using Poco::ThreadPool;  

9.   using Poco::Runnable;  

10.  using Poco::AutoPtr;  

11.  class WorkNotification: public Notification  

12.  {  

13.  public:  

14.      WorkNotification(int data): _data(data) {}  

15.      int data() const  

16.      {  

17.          return _data;  

18.      }  

19.  private:  

20.      int _data;  

21.  };  

22.    

23.    

24.  class Worker: public Runnable  

25.  {  

26.  public:  

27.      Worker(NotificationQueue& queue): _queue(queue) {}  

28.      void run()  

29.      {  

30.          AutoPtr pNf(_queue.waitDequeueNotification());  

31.          while (pNf)  

32.          {  

33.              WorkNotification* pWorkNf =  

34.                  dynamic_cast(pNf.get());  

35.              if (pWorkNf)  

36.              {  

37.                  // do some work  

38.              }  

39.              pNf = _queue.waitDequeueNotification();  

40.          }  

41.      }  

42.  private:  

43.      NotificationQueue& _queue;  

44.  };  

45.    

46.    

47.  int main(int argc, char** argv)  

48.  {  

49.      NotificationQueue queue;  

50.      Worker worker1(queue); // create worker threads  

51.      Worker worker2(queue);  

52.      ThreadPool::defaultPool().start(worker1); // start workers  

53.      ThreadPool::defaultPool().start(worker2);  

54.      // create some work  

55.      for (int i = 0; i < 100; ++i)  

56.      {  

57.          queue.enqueueNotification(new WorkNotification(i));  

58.      }  

59.      while (!queue.empty()) // wait until all work is done  

60.          Poco::Thread::sleep(100);  

61.      queue.wakeUpAll(); // tell workers they're done  

62.      ThreadPool::defaultPool().joinAll();  

63.      return 0;  

64.  }  



4.3 异步通知的类图

        最后给出异步通知的类图:

POCO C++库学习和分析 -- 通知和事件 (四)

5. 事件

        Poco中的事件和代理概念来自于C#。对于事件的使用者,也就是调用方来说,用法非常的简单。


5.1 从例子说起

        首先让我们来看一个同步事件例子,然后再继续我们的讨论:

[cpp] view plaincopy

1.   #include "Poco/BasicEvent.h"  

2.   #include "Poco/Delegate.h"  

3.   #include   

4.     

5.   using Poco::BasicEvent;  

6.   using Poco::Delegate;  

7.     

8.   class Source  

9.   {  

10.  public:  

11.      BasicEvent<int> theEvent;  

12.      void fireEvent(int n)  

13.      {  

14.          theEvent(this, n);  

15.          // theEvent.notify(this, n); // alternative syntax  

16.      }  

17.  };  

18.    

19.  class Target  

20.  {  

21.  public:  

22.      void onEvent(const void* pSender, int& arg)  

23.      {  

24.          std::cout << "onEvent: " << arg << std::endl;  

25.      }  

26.  };  

27.    

28.  int main(int argc, char** argv)  

29.  {  

30.      Source source;  

31.      Target target;  

32.      source.theEvent += Poco::delegate(&target, &Target::onEvent);  

33.      source.fireEvent(42);  

34.      source.theEvent -= Poco::delegate(&target, &Target::onEvent);  

35.    

36.      return 0;  

37.  }  


        从上面的代码里,我们可以清晰的看到几个部分,数据源Source,事件BasicEvent,目标对象Target。

        其中source.theEvent += Poco::delegate(&target, &Target::onEvent)完成了,目标向数据源事件注册的过程。大家都知道在C++中,程序运行是落实到类的实例的,看一下消息传递的过程,Poco是如何解决这个问题。target是目标对象实例,Target::onEvent目标对象处理事件的函数入口地址。source.fireEvent(42)触发事件运行,其定义为:

[cpp] view plaincopy

1.   void fireEvent(int n)  

2.   {  

3.       theEvent(this, n);  

4.       // theEvent.notify(this, n); // alternative syntax  

5.   }  

        theEvent(this,n)中存在两个参数,其中n为Target::onEvent(const void* pSender, int& arg)处理函数的参数,可理解为消息或者事件内容;this给出了触发源实例的信息。
ok。这样消息的传递流程出来了。消息源实例的地址,消息内容,目标实例地址,目标实例类的处理函数入口地址。使用者填入上述信息就可以传递消息了。相当简单。

        而对于事件的开发者,如何实现上述功能。这是另外一码事,用C++实现这么一个功能还是挺复杂的一件事。看一下使用语言的方式,想一下用到的C++技术:
        1. +=/-= 重载

[cpp] view plaincopy

1.   source.theEvent += Poco::delegate(&target, &Target::onEvent);  

        2. 仿函式

[cpp] view plaincopy

1.   theEvent(this, n);  

        3. 模板
        开发者是不应该限定使用者发送消息的类以及接受消息类的类型的,因此C++中能够完成此功能的技术只有模板了。关于模板编程还想聊上几句。STL的特点在于算法和数据结构的分离,这个其实也是泛型编程的特点。如果把使用者对于类的应用过程看做算法过程的话,就可以对这个过程进行泛型编程。同时应该注意的是,算法和数据结构是存在关联的,这是隐含在泛型编程中的,能够使用某种算法的数据结构一定是符合该种算法要求的。
        就拿Poco中事件的委托Delegate来说,目标对象处理事件的函数入口是存在某种假设的。Poco中假设入口函数必须是如下形式之一:

[cpp] view plaincopy

1.   void (TObj::*NotifyMethod)(const void*, TArgs&);  

2.   void (TObj::*NotifyMethod)(TArgs&);  

3.   void (*NotifyMethod)(const void*, TArgs&);  

4.   void (*NotifyMethod)(void*, TArgs&);  



5.2 事件的实现

        下面一张图是Poco中Event的类图:


        下面另一张图是Poco中Event流动的过程:




        从图上看实现事件的类被分成了几类:
        1) Delegate: 

            AbstractDelegate,Delegate,Expire,FunctionDelegate,AbstractPriorityDelegate,PriorityDelegate,FunctionPriorityDelegate:
        2) Strategy:
             NotificationStrategy,PriorityStrategy,DefaultStrategy,FIFOStrategy
        3) Event:
            AbstractEvent,PriorityEvent,FIFOEvent,BasicEvent

        我们取Delegate,DefaultStrategy,BasicEvent来分析,其他的只是在它们的基础上加了一些修饰,流程类似。

        Delegate类定义如下:

[cpp] view plaincopy

1.   template <class TObj, class TArgs, bool withSender = true>   

2.   class Delegate: public AbstractDelegate  

3.   {  

4.   public:  

5.       typedef void (TObj::*NotifyMethod)(const void*, TArgs&);  

6.     

7.       Delegate(TObj* obj, NotifyMethod method):  

8.           _receiverObject(obj),   

9.           _receiverMethod(method)  

10.      {  

11.      }  

12.    

13.      Delegate(const Delegate& delegate):  

14.          AbstractDelegate(delegate),  

15.          _receiverObject(delegate._receiverObject),  

16.          _receiverMethod(delegate._receiverMethod)  

17.      {  

18.      }  

19.    

20.      ~Delegate()  

21.      {  

22.      }  

23.        

24.      Delegate& operator = (const Delegate& delegate)  

25.      {  

26.          if (&delegate != this)  

27.          {  

28.              this->_receiverObject = delegate._receiverObject;  

29.              this->_receiverMethod = delegate._receiverMethod;  

30.          }  

31.          return *this;  

32.      }  

33.    

34.      bool notify(const void* sender, TArgs& arguments)  

35.      {  

36.          Mutex::ScopedLock lock(_mutex);  

37.          if (_receiverObject)  

38.          {  

39.              (_receiverObject->*_receiverMethod)(sender, arguments);  

40.              return true;  

41.          }  

42.          else return false;  

43.      }  

44.    

45.      bool equals(const AbstractDelegate& other) const  

46.      {  

47.          const Delegate* pOtherDelegate = reinterpret_cast<const Delegate*>(other.unwrap());  

48.          return pOtherDelegate && _receiverObject == pOtherDelegate->_receiverObject && _receiverMethod == pOtherDelegate->_receiverMethod;  

49.      }  

50.    

51.      AbstractDelegate* clone() const  

52.      {  

53.          return new Delegate(*this);  

54.      }  

55.        

56.      void disable()  

57.      {  

58.          Mutex::ScopedLock lock(_mutex);  

59.          _receiverObject = 0;  

60.      }  

61.    

62.  protected:  

63.      TObj*        _receiverObject;  

64.      NotifyMethod _receiverMethod;  

65.      Mutex        _mutex;  

66.    

67.    

68.  private:  

69.      Delegate();  

70.  };  


        我们可以看到Delegate类中存储了目标类实例的指针_receiverObject,同时存储了目标类处理函数的入口地址_receiverMethod,当初始化Delegate实例时,参数被带进。
Delegate类中处理事件的函数为bool notify(const void* sender, TArgs&arguments),这是一个虚函数. 如果去看它的实现的话,它最终调用了目标类处理函数

(_receiverObject->*_receiverMethod)(sender,arguments)。如果用简单的话来描述Delegate的作用,那就是目标类的代理。


        在Poco中对于Delegate提供了模板函数delegate,来隐藏Delegate对象的创建,其定义如下:

[cpp] view plaincopy

1.   template <class TObj, class TArgs>  

2.   static Delegatetrue> delegate(TObj* pObj, void (TObj::*NotifyMethod)(const void*, TArgs&))  

3.   {  

4.       return Delegatetrue>(pObj, NotifyMethod);  

5.   }  


        在来看DefaultStrategy类,其定义如下:

[cpp] view plaincopy

1.   template <class TArgs, class TDelegate>   

2.   class DefaultStrategy: public NotificationStrategy  

3.       /// Default notification strategy.  

4.       ///  

5.       /// Internally, a std::vector<> is used to store  

6.       /// delegate objects. Delegates are invoked in the  

7.       /// order in which they have been registered.  

8.   {  

9.   public:  

10.      typedef SharedPtr         DelegatePtr;  

11.      typedef std::vector     Delegates;  

12.      typedef typename Delegates::iterator Iterator;  

13.    

14.  public:  

15.      DefaultStrategy()  

16.      {  

17.      }  

18.    

19.      DefaultStrategy(const DefaultStrategy& s):  

20.          _delegates(s._delegates)  

21.      {  

22.      }  

23.    

24.      ~DefaultStrategy()  

25.      {  

26.      }  

27.    

28.      void notify(const void* sender, TArgs& arguments)  

29.      {  

30.          for (Iterator it = _delegates.begin(); it != _delegates.end(); ++it)  

31.          {  

32.              (*it)->notify(sender, arguments);  

33.          }  

34.      }  

35.    

36.      void add(const TDelegate& delegate)  

37.      {  

38.          _delegates.push_back(DelegatePtr(static_cast(delegate.clone())));  

39.      }  

40.    

41.      void remove(const TDelegate& delegate)  

42.      {  

43.          for (Iterator it = _delegates.begin(); it != _delegates.end(); ++it)  

44.          {  

45.              if (delegate.equals(**it))  

46.              {  

47.                  (*it)->disable();  

48.                  _delegates.erase(it);  

49.                  return;  

50.              }  

51.          }  

52.      }  

53.    

54.      DefaultStrategy& operator = (const DefaultStrategy& s)  

55.      {  

56.          if (this != &s)  

57.          {  

58.              _delegates = s._delegates;  

59.          }  

60.          return *this;  

61.      }  

62.    

63.      void clear()  

64.      {  

65.          for (Iterator it = _delegates.begin(); it != _delegates.end(); ++it)  

66.          {  

67.              (*it)->disable();  

68.          }  

69.          _delegates.clear();  

70.      }  

71.    

72.      bool empty() const  

73.      {  

74.          return _delegates.empty();  

75.      }  

76.    

77.  protected:  

78.      Delegates _delegates;  

79.  };  


        哦,明白了,DefaultStrategy是一组委托的集合,内部存在的_delegates定义如下:

[cpp] view plaincopy

1.   std::vector>  _delegate  

        DefaultStrategy可以被理解成一组目标的代理。在DefaultStrategy的notify函数中,我们可以设定,当一个事件发生,要送给多个目标时,所采取的策略。NotificationStrategy,PriorityStrategy,DefaultStrategy,FIFOStrategy之间的区别也就在于此。

        最后来看一下BasicEvent类。它的定义是:

[cpp] view plaincopy

1.   template <class TArgs, class TMutex = FastMutex>   

2.   class BasicEvent: public AbstractEvent <   

3.       TArgs, DefaultStrategy >,  

4.       AbstractDelegate,  

5.       TMutex  

6.   >  

7.       /// A BasicEvent uses the DefaultStrategy which   

8.       /// invokes delegates in the order they have been registered.  

9.       ///  

10.      /// Please see the AbstractEvent class template documentation  

11.      /// for more information.  

12.  {  

13.  public:  

14.      BasicEvent()  

15.      {  

16.      }  

17.    

18.      ~BasicEvent()  

19.      {  

20.      }  

21.    

22.  private:  

23.      BasicEvent(const BasicEvent& e);  

24.      BasicEvent& operator = (const BasicEvent& e);  

25.  };  

26.    

27.  AbstractEvent定义为:  

28.  template <class TArgs, class TStrategy, class TDelegate, class TMutex = FastMutex>   

29.  class AbstractEvent  

30.  {  

31.  public:  

32.      AbstractEvent():   

33.          _executeAsync(this, &AbstractEvent::executeAsyncImpl),  

34.          _enabled(true)  

35.      {  

36.      }  

37.    

38.      AbstractEvent(const TStrategy& strat):   

39.          _executeAsync(this, &AbstractEvent::executeAsyncImpl),  

40.          _strategy(strat),  

41.          _enabled(true)  

42.      {     

43.      }  

44.    

45.      virtual ~AbstractEvent()  

46.      {  

47.      }  

48.    

49.      void operator += (const TDelegate& aDelegate)  

50.      {  

51.          typename TMutex::ScopedLock lock(_mutex);  

52.          _strategy.add(aDelegate);  

53.      }  

54.        

55.      void operator -= (const TDelegate& aDelegate)  

56.      {  

57.          typename TMutex::ScopedLock lock(_mutex);  

58.          _strategy.remove(aDelegate);  

59.      }  

60.        

61.      void operator () (const void* pSender, TArgs& args)  

62.      {  

63.          notify(pSender, args);  

64.      }  

65.        

66.      void operator () (TArgs& args)  

67.      {  

68.          notify(0, args);  

69.      }  

70.    

71.      void notify(const void* pSender, TArgs& args)  

72.      {  

73.          Poco::ScopedLockWithUnlock lock(_mutex);  

74.            

75.          if (!_enabled) return;  

76.            

77.          TStrategy strategy(_strategy);  

78.          lock.unlock();  

79.          strategy.notify(pSender, args);  

80.      }  

81.    

82.      ActiveResult notifyAsync(const void* pSender, const TArgs& args)  

83.      {  

84.          NotifyAsyncParams params(pSender, args);  

85.          {  

86.              typename TMutex::ScopedLock lock(_mutex);  

87.                    

88.              params.ptrStrat = SharedPtr(new TStrategy(_strategy));  

89.              params.enabled  = _enabled;  

90.          }  

91.          ActiveResult result = _executeAsync(params);  

92.          return result;  

93.      }  

94.          // .......  

95.    

96.  protected:  

97.      struct NotifyAsyncParams  

98.      {  

99.          SharedPtr ptrStrat;  

100.         const void* pSender;  

101.         TArgs       args;  

102.         bool        enabled;  

103.           

104.         NotifyAsyncParams(const void* pSend, const TArgs& a):ptrStrat(), pSender(pSend), args(a), enabled(true)  

105.         {  

106.         }  

107.     };  

108.   

109.     ActiveMethod _executeAsync;  

110.   

111.     TArgs executeAsyncImpl(const NotifyAsyncParams& par)  

112.     {  

113.         if (!par.enabled)  

114.         {  

115.             return par.args;  

116.         }  

117.   

118.   

119.         NotifyAsyncParams params = par;  

120.         TArgs retArgs(params.args);  

121.         params.ptrStrat->notify(params.pSender, retArgs);  

122.         return retArgs;  

123.     }  

124.   

125.     TStrategy _strategy; /// The strategy used to notify observers.  

126.     bool      _enabled;  /// Stores if an event is enabled. Notfies on disabled events have no effect  

127.                          /// but it is possible to change the observers.  

128.     mutable TMutex _mutex;  

129.   

130. private:  

131.     AbstractEvent(const AbstractEvent& other);  

132.     AbstractEvent& operator = (const AbstractEvent& other);  

133. };  


        从AbstractEvent类中,我们看到AbstractEvent类中存在了一个TStrategy的对象_strategy。接口上则重载了+=函数,用来把所需的目标对象加入_strategy中,完成注册功能。重载了operator (),用于触发事件。
        于是同步事件所有的步骤便被串了起来。


5.2 异步事件

        理解了同步事件后,让我们来看异步事件。这还是让我们从一个例子说起:

[cpp] view plaincopy

1.   #include "Poco/BasicEvent.h"  

2.   #include "Poco/Delegate.h"  

3.   #include "Poco/ActiveResult.h"  

4.   #include   

5.     

6.   using Poco::BasicEvent;  

7.   using Poco::Delegate;  

8.   using Poco::ActiveResult;  

9.     

10.  class TargetAsync  

11.  {  

12.  public:  

13.      void onAsyncEvent(const void* pSender, int& arg)  

14.      {  

15.          std::cout << "onAsyncEvent: " << arg <<  " Current Thread Id is :" << GetCurrentThreadId() << " "<< std::endl;  

16.          return;  

17.      }  

18.  };  

19.    

20.  template<typename RT> class Source  

21.  {  

22.  public:  

23.      BasicEvent<int> theEvent;  

24.      ActiveResult AsyncFireEvent(int n)  

25.      {  

26.          return ActiveResult (theEvent.notifyAsync(this, n));  

27.      }  

28.  };  

29.    

30.  int main(int argc, char** argv)  

31.  {  

32.      Source<int> source;  

33.      TargetAsync target;  

34.      std::cout <<  "Main Thread Id is :" << GetCurrentThreadId() << " " << std::endl;  

35.      source.theEvent += Poco::delegate(&target, &TargetAsync::onAsyncEvent);  

36.      ActiveResult<int> Targs = source.AsyncFireEvent(43);  

37.      Targs.wait();  

38.      std::cout << "onEventAsync: " << Targs.data() << std::endl;  

39.      source.theEvent -= Poco::delegate(&target, &TargetAsync::onAsyncEvent);  

40.    

41.      return 0;  

42.  }  

        例子里可以看出,同同步事件不同的是,触发事件时,我们调用的是notifyAsync接口。在这个接口里,NotifyAsyncParams对象被创建,并被交由一个主动对象_executeAsync执行。关于主动对象ActiveMethod的介绍,可以从前面的文章POCOC++库学习和分析 -- 线程(四)中找到。

 

POCO C++库学习和分析 -- 数据类型转换

 

         文章写到这里,Foundation库中的功能已经介绍过半了。在接下去介绍其他模块之前,我们先来回顾一下前面的内容。前面的内容包括了:

         1. SharedLibrary模块(插件技术)  《FoundationSharedLibrary模块分析

         2. 线程(锁,线程,线程池,定时器,任务,主动对象) 《线程

         3. 内存管理(智能指针,内存池,自动释放的对象池,对象工厂)  《内存管理

         4. 进程(进程,进程通讯)  《进程

         5. 消息和事件(同步/异步消息传递,消息队列)  《通知和事件

        有了这些模块,我们就可以搭建起一个本地程序的框架了(当然这不包括绘图和显示,Poco库不提供这些功能)。程序的框架很重要,就如同人的骨架和血液一样,决定了一个程序的结构,间接的影响了程序的可修改性和可维护性,但这还不够,要写出一个完整的程序,我们还需要其他的一些部分,这些部分也很重要,就如同人的肌肉和衣服。

         下面介绍Foundation库中关于转换的几个类:


1. ByteOrder

         ByteOrder提供了一系列的静态函数用于字节序的转换。在使用这个类之前,让我们先了解一下它所解决问题。它主要用来解决big-endian和litter-endian的问题。

1.1 big-endian和litter-endian

         big-endian和litter-endian指的是读取存储时的数据解释方式。它们只和多字节类型的数据有关,比如说int,short,long型,而对单字节数据byte却没有影响。
                    litter-endian:将低序字节存储在起始地址(低位字节存储在内存中低位地址)。

                    big-endian:将高序字节存储在起始地址(高位字节存储在内存中低位地址)。

 

         举个例子,int a = 0x01020304
         在BIG-ENDIAN的情况下存放为:
                     字节号  0        1        2      3
                数据    01      02      03   04
         在LITTLE-ENDIAN的情况下存放为:
                    字节号  0         1        2     3
              数据    04        03      02   01


          再举一个,int a =0x1234abcd
         在BIG-ENDIAN的情况下存放为:
                    字节号  0      1      2      3
              数据    12     34    ab    cd
         在LITTLE-ENDIAN的情况下存放为:
                   字节号  0       1     2      3
             数据    cd      ab    34    12


1.2 主机序和网络序

         主机序是读取计算机内存数据时的解释方式,它和CPU、操作系统的类型相关,分为litter-endian和big-endian。X86架构的cpu不管操作系统是NT还是UNIX系的,都是小字节序,而PowerPC 、SPARC和Motorola处理器则很多是大字节序。下面是一张字节序和CPU、操作系统的关系表。

         处理器                     操作系统           字节排序
         Alpha                       全部                Little endian
         HP-PA                     NT                   Little endian
         HP-PA                     UNIX               Big endian
         Intelx86                  全部                 Little endian (x86系统是小端字节序系统)
         Motorola680x()     全部                  Bigendian
         MIPS                       NT                   Littleendian
         MIPS                      UNIX                Big endian
         PowerPC               NT                   Little endian
         PowerPC               非NT               Big endian   (PPC系统是大端字节序系统)
         RS/6000                UNIX               Big endian
         SPARC                  UNIX                Big endian
         IXP1200                ARM核心         全部 Little endian


1.3 主机序和网络序和大头小头引起的问题

        如果在两台字节序不同的主机之间进行网络通讯,大小字节序的问题就会出现。通常的做法是在小字节序一端的主机进行处理(网络序始终是大字节序),小字节序的主机在发送数据前,转换数据为大字节序,而在接受时,把大字节序数据转成小字节序。

         如果在字节序相同的两台机器之间进行通讯,可以不用考虑字节序问题。
         同样的是在两台机器之间,用java语言编写通讯程序时,可以不考虑字节序问题。JAVA字节序与网络字节序都是big-endian.


1.4 ByteOrder静态函数

         ByteOrder提供了一组静态的Api去解决这个问题。
         1) IntXXflipBytes(IntXX value)
             字节翻转排序,实现大小字节序的转换
         2) IntXXtoBigEndian(IntXX value)
             把数据从本机序转成大字节序。如果本机序是本身就是大字节序,返回结果为转之前数据。
         3) IntXXtoLittleEndian(IntXX value)
             把数据从本机序转成小字节序。如果本机序是本身就是小字节序,返回结果为转之前数据。
         4) IntXXfromBigEndian(IntXX value)
             把数据从大字节序转成本机序。如果本机序是本身就是大字节序,返回结果为转之前数据。
         5) IntXXfromLittleEndian(IntXX value)
             把数据从小字节序转成本机序。如果本机序是本身就是小字节序,返回结果为转之前数据。
         6) IntXXtoNetwork(IntXX value)
             把数据从本机序转成网络序。如果本机序是本身就是大字节序,返回结果为转之前数据。
         7) IntXXfromNetwork(IntXX value)
             把数据从网络序转成本机序。如果本机序是本身就是大字节序,返回结果为转之前数据。

             下面来看一个ByteOrder的例子:

[cpp] view plaincopy

1.   #include "Poco/ByteOrder.h"  

2.   #include   

3.   using Poco::ByteOrder;  

4.   using Poco::UInt16;  

5.   int main(int argc, char** argv)  

6.   {  

7.   #ifdef POCO_ARCH_LITTLE_ENDIAN  

8.       std::cout << "little endian" << std::endl;  

9.   #else  

10.      std::cout << "big endian" << std::endl;  

11.  #endif  

12.      UInt16 port = 80;  

13.      UInt16 networkPort = ByteOrder::toNetwork(port);  

14.      return 0;  

15.  }  



2. Any

         Poco中的Any类,来自于Boost库中的Any类。Any类主要用于数据库读取时的数据保存和解析。它能够将任意类型值保存进去,并能把任意类型值读出来。Boost::any的作者认为,所谓generic type有三个层面的解释方法:
         1. 类似variant类型那样任意进行类型转换,可以保存一个(int)5进去,读一个(string)"5"出来。在variant类型内部使用union实现,使用灵活但效率较低。
         2. 区别对待包含值的类型,保存一个(int)5进去,不会被隐式转换为(string)'5'或者(double)5.0,读出来还是(int)5。这样效率较高且类型安全,不必担心ambiguousconversions
         3. 对包含值类型不加区别,例如把所有保存的值强制转换为void*保存。读取时再有程序员判断其类型。这样效率虽最高但无法保证类型安全

 

         boost::any就选择了第二层面的设计思路,它允许用户将任意类型值保存进一个any类型变量,但内部并不改变值的类型,并提供方法让用户在使用时主动/被动进行类型判断。关于Poco::Any的进一步描述和实现技巧,可以看刘未鹏大大的《boost源码剖析之:泛型指针类any之海纳百川》和hityct1大大的《boost::any的用法、优点和缺点以及源代码分析》。

         下面是Poco::Any的一个例子:

[cpp] view plaincopy

1.   #include "Poco/Any.h"  

2.   #include "Poco/Exception.h"  

3.   #include   

4.     

5.   using Poco::Any;  

6.   using Poco::AnyCast;  

7.   using Poco::RefAnyCast;  

8.     

9.   int main(int argc, char** argv)  

10.  {  

11.      Any any(42);  

12.      int i = AnyCast<int>(any);              // okay  

13.      int& ri = RefAnyCast<int>(any);         // okay  

14.      try  

15.      {  

16.          short s = AnyCast<short>(any);  // throws BadCastException  

17.                  assert(any.type() == typeid(int));    

18.      }  

19.      catch (Poco::BadCastException&)  

20.      {}  

21.      return 0;  

22.  }  


         最后给出Poco::Any的类图





3. DynamicAny

        Poco::DynamicAny在generic type的处理思路上采用的是上述第一种和第二种思路的结合。
         首先它支持有限类型之间的自动类型转换,可以保存一个(int)5进去,读一个(string)"5"出来。所谓有限类型很好理解,因为类型转化的本质是对内存数据的不同解释,如果转化前的数据类型和转化后的数据类型都是不定且无限,作为类的书写者,实在是不能想象的。而有限类型的转化至少我们可以枚举,而事实上这正是Poco::DynamicAny实现时所做的。Poco::DynamicAny支持Int8、Int16、Int32、Int64UInt8、UInt16、UInt32、UInt64、bool、float、double、char、std::string、long、unsigned long、std::vector、DateTime、LocalDateTime、Timestamp类型之间的转化。为此Poco::DynamicAny提供了成员函数convert和operator T()函数去实现上述的功能。当转换失败的时候会抛出异常。
         第二,在有限类型内部,Poco::DynamicAny提供函数完成与Poco::Any类类似的功能。事实上DynamicAny::extract()函数和Any类的友元函数AnyCast()是基本一致的。下面是二者代码:

[cpp] view plaincopy

1.   template <typename T> const T& DynamicAny::extract() const  

2.           /// Returns a const reference to the actual value.  

3.           ///  

4.           /// Must be instantiated with the exact type of  

5.           /// the stored value, otherwise a BadCastException  

6.           /// is thrown.  

7.           /// Throws InvalidAccessException if DynamicAny is empty.  

8.   {  

9.       if (_pHolder && _pHolder->type() == typeid(T))  

10.      {  

11.          DynamicAnyHolderImpl* pHolderImpl = static_cast*>(_pHolder);  

12.          return pHolderImpl->value();  

13.      }  

14.      else if (!_pHolder)  

15.          throw InvalidAccessException("Can not extract empty value.");  

16.      else  

17.          throw BadCastException(format("Can not convert %s to %s.",  

18.              _pHolder->type().name(),  

19.              typeid(T).name()));  

20.  }  

 

[cpp] view plaincopy

1.   template <typename ValueType>  

2.   ValueType* AnyCast(Any* operand)  

3.       /// AnyCast operator used to extract the ValueType from an Any*. Will return a   

4.     

5.     

6.   pointer  

7.       /// to the stored value.   

8.       ///  

9.       /// Example Usage:   

10.      ///     MyType* pTmp = AnyCast(pAny).  

11.      /// Will return NULL if the cast fails, i.e. types don't match.  

12.  {  

13.      return operand && operand->type() == typeid(ValueType)  

14.                  ? &static_cast*>(operand->_content)->_held  

15.                  : 0;  

16.  }  


         看到这里,我们实际上就明白了Poco::DynamicAny和Poco::Any的使用场景。对于用户自建数据类型,毫无疑问只能使用Poco::Any类。而对于C++语言内置的数据类型,使用Poco::DynamicAny,因为Poco::DynamicAny不仅对于内置数据类型提供了类似Poco::Any的接口,而且还提供了相互之间的转换功能。
         在Poco::DynamicAny的实现上,使用了模板特化技术,用于在不同数据类型之间的转换,关于这一点,也可以理解成枚举。实质上就是说把程序员在不同数据间的转换工作在Poco::DynamicAny类中先实现了一遍,程序员只需要直接调用Poco::DynamicAny就可以了。

         对于convert()/operatorT()函数和extract()函数的区别如下:
             T convert()/operator T()/void convert(T& val)             const T& extract()
               返回一个拷贝                                                              返回一个常量引用
               自动转变类型                                                              不会自动转变类型
               比Any慢                                                                       同Any一样快

         下面是Poco::DynamicAny的类图:




         在介绍完这个类之前提一句,有兴趣的同学也可以去考察一下boost库中的boost::variant和boost::lexical_cast,看一看它们和Poco::DynamicAny的异同。

 

POCO C++库学习和分析 -- 哈希

 

1. Hash概论

        在理解Poco中的Hash代码之前,首先需要了解一下Hash的基本理论。下面的这些内容和教课书上的内容并没有太大的差别。


1.1 定义

        下面这几段来自于百度百科:
        Hash:一般翻译做"散列",也有直接音译为"哈希"的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
        Hashtable:散列表,也叫哈希表,是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
               * 若结构中存在关键字和K相等的记录,则必定存储在f(K)的位置上。由此,不需比较便可直接取得所查记录。这个对应关系f称为散列函数(Hash function),按这个思想建立的表为散列表。
               * 对不同的关键字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),这种现象称冲突。具有相同函数值的关键字对该散列函数来说称做同义词。
               * 综上所述,根据散列函数H(key)和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“象”, 作为这条记录在表中的存储位置,这种表便称为散列表,这一映象过程称为散列造表或散列,所得的存储位置称散列地址。这个现象也叫散列桶,在散列桶中,只能通过顺序的方式来查找,一般只需要查找三次就可以找到。科学家计算过,当重载因子不超过75%,查找效率最高。
        * 若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少冲突。


1.2 Hash table查找效率

        对于Hash table来言,理论上查找效率为O(1)。但在现实世界中,查找的过程存在冲突现象。产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。影响产生冲突多少有以下三个因素:
        1. 散列函数是否均匀;
        2. 处理冲突的方法;
        3. 散列表的装填因子。
        散列表的装填因子定义为:α= 填入表中的元素个数 / 散列表的长度
        实际上,散列表的平均查找长度是装填因子α的函数,只是不同处理冲突的方法有不同的函数。


1.3 Poco中的Hash内容

        Poco中的hash内容主要关注于Hash表的应用。下面是Poco中相关于Hash的类图:


        我们看到Poco的Hash内容主要被分成3部分:
        1. Hash函数。Poco提供了一组Hash函数用于,生成hash值。同时提供了模板类HashFunction,通过仿函式提供对任意数据结构生成hash值的功能。
        2. Hashtable(哈希表)。Poco中实现了3种哈希表,分别是SimpleHashTable, HashTable,LinearHashTable。
        3. 在哈希表上的应用,封装出hash map和hash set。



2. Hash函数

        Hash函数是解决hash冲突的第一个要素。
        Poco中提供了一组Hash函数,用于产生hash值。其定义如下:

[cpp] view plaincopy

1.   inline std::size_t hash(Int8 n)  

2.   {  

3.       return static_castsize_t>(n)*2654435761U;   

4.   }  

5.     

6.   inline std::size_t hash(UInt8 n)  

7.   {  

8.       return static_castsize_t>(n)*2654435761U;   

9.   }  

10.    

11.  inline std::size_t hash(Int16 n)  

12.  {  

13.      return static_castsize_t>(n)*2654435761U;   

14.  }  

15.    

16.    

17.  inline std::size_t hash(UInt16 n)  

18.  {  

19.      return static_castsize_t>(n)*2654435761U;   

20.  }  

21.    

22.  inline std::size_t hash(Int32 n)  

23.  {  

24.      return static_castsize_t>(n)*2654435761U;   

25.  }  

26.    

27.  inline std::size_t hash(UInt32 n)  

28.  {  

29.      return static_castsize_t>(n)*2654435761U;   

30.  }  

31.    

32.    

33.  inline std::size_t hash(Int64 n)  

34.  {  

35.      return static_castsize_t>(n)*2654435761U;   

36.  }  

37.    

38.  inline std::size_t hash(UInt64 n)  

39.  {  

40.      return static_castsize_t>(n)*2654435761U;   

41.  }  

42.    

43.  std::size_t hash(const std::string& str)  

44.  {  

45.      std::size_t h = 0;  

46.      std::string::const_iterator it  = str.begin();  

47.      std::string::const_iterator end = str.end();  

48.      while (it != end)  

49.      {  

50.          h = h * 0xf4243 ^ *it++;  

51.      }  

52.      return h;  

53.  }  

        这里就不对hash函数做过多叙述了,下面列出一些其他的常用hash函数。网上有专门的论述,并对不同的hash函数效果做了比较,有兴趣的话可以google一下。
        附:各种哈希函数的C语言程序代码

[cpp] view plaincopy

1.   unsigned int SDBMHash(char *str)  

2.   {  

3.       unsigned int hash = 0;  

4.       while (*str)  

5.       {  

6.           // equivalent to: hash = 65599*hash + (*str++);  

7.           hash = (*str++) + (hash << 6) + (hash << 16) - hash;  

8.       }  

9.       return (hash & 0x7FFFFFFF);  

10.  }  

11.    

12.    

13.  // RS Hash   

14.  unsigned int RSHash(char *str)  

15.  {  

16.      unsigned int b = 378551;  

17.      unsigned int a = 63689;  

18.      unsigned int hash = 0;  

19.      while (*str)  

20.      {  

21.          hash = hash * a + (*str++);  

22.          a *= b;  

23.      }  

24.      return (hash & 0x7FFFFFFF);  

25.  }  

26.    

27.    

28.  // JS Hash   

29.  unsigned int JSHash(char *str)  

30.  {  

31.      unsigned int hash = 1315423911;  

32.      while (*str)  

33.      {  

34.          hash ^= ((hash << 5) + (*str++) + (hash >> 2));  

35.      }  

36.      return (hash & 0x7FFFFFFF);  

37.  }  

38.    

39.    

40.  // P. J. Weinberger Hash   

41.  unsigned int PJWHash(char *str)  

42.  {  

43.      unsigned int BitsInUnignedInt = (unsigned int)(sizeof(unsigned int) * 8);  

44.      unsigned int ThreeQuarters  = (unsigned int)((BitsInUnignedInt  * 3) / 4);  

45.      unsigned int OneEighth = (unsigned int)(BitsInUnignedInt / 8);  

46.      unsigned int HighBits = (unsigned int)(0xFFFFFFFF) << (BitsInUnignedInt - OneEighth);  

47.      unsigned int hash   = 0;  

48.      unsigned int test   = 0;  

49.      while (*str)  

50.      {  

51.          hash = (hash << OneEighth) + (*str++);  

52.          if ((test = hash & HighBits) != 0)  

53.          {  

54.              hash = ((hash ^ (test >> ThreeQuarters)) & (~HighBits));  

55.          }  

56.      }  

57.      return (hash & 0x7FFFFFFF);  

58.  }  

59.    

60.    

61.  // ELF Hash   

62.  unsigned int ELFHash(char *str)  

63.  {  

64.      unsigned int hash = 0;  

65.      unsigned int x  = 0;  

66.      while (*str)  

67.      {  

68.          hash = (hash << 4) + (*str++);  

69.          if ((x = hash & 0xF0000000L) != 0)  

70.          {  

71.              hash ^= (x >> 24);  

72.              hash &= ~x;  

73.          }  

74.      }  

75.      return (hash & 0x7FFFFFFF);  

76.  }  

77.    

78.    

79.  // BKDR Hash   

80.  unsigned int BKDRHash(char *str)  

81.  {  

82.      unsigned int seed = 131; // 31 131 1313 13131 131313 etc..  

83.      unsigned int hash = 0;  

84.      while (*str)  

85.      {  

86.          hash = hash * seed + (*str++);  

87.      }  

88.      return (hash & 0x7FFFFFFF);  

89.  }  

90.    

91.    

92.  // DJB Hash   

93.  unsigned int DJBHash(char *str)  

94.  {  

95.      unsigned int hash = 5381;  

96.      while (*str)  

97.      {  

98.          hash += (hash << 5) + (*str++);  

99.      }  

100.     return (hash & 0x7FFFFFFF);  

101. }  

102.   

103.   

104. // AP Hash   

105. unsigned int APHash(char *str)  

106. {  

107.     unsigned int hash = 0;  

108.     int i;  

109.     for (i=0; *str; i++)  

110.     {  

111.         if ((i & 1) == 0)  

112.         {  

113.             hash ^= ((hash << 7) ^ (*str++) ^ (hash >> 3));  

114.         }  

115.         else  

116.         {  

117.             hash ^= (~((hash << 11) ^ (*str++) ^ (hash >> 5)));  

118.         }  

119.     }  

120.     return (hash & 0x7FFFFFFF);  

121. }  

122.   

123.   

124. unsigned int hash(char *str)  

125. {  

126.     register unsigned int h;  

127.     register unsigned char *p;  

128.     for(h=0, p = (unsigned char *)str; *p ; p++)  

129.         h = 31 * h + *p;  

130.     return h;  

131. }  

 

[cpp] view plaincopy

1.   // PHP中出现的字符串Hash函数  

2.   static unsigned long hashpjw(char *arKey, unsigned int nKeyLength)  

3.   {  

4.       unsigned long h = 0, g;  

5.       char *arEnd=arKey+nKeyLength;  

6.     

7.       while (arKey < arEnd) {  

8.           h = (h << 4) + *arKey++;  

9.           if ((g = (h & 0xF0000000))) {  

10.              h = h ^ (g >> 24);  

11.              h = h ^ g;  

12.          }  

13.      }  

14.      return h;  

15.  }  

 

[cpp] view plaincopy

1.   // OpenSSL中出现的字符串Hash函数  

2.   unsigned long lh_strhash(char *str)  

3.   {  

4.       int i,l;  

5.       unsigned long ret=0;  

6.       unsigned short *s;  

7.     

8.       if (str == NULL) return(0);  

9.       l=(strlen(str)+1)/2;  

10.      s=(unsigned short *)str;  

11.      for (i=0; i  

12.          ret^=(s[i]<<(i&0x0f));  

13.          return(ret);  

14.  }   

15.    

16.  /* The following hash seems to work very well on normal text strings 

17.  * no collisions on /usr/dict/words and it distributes on %2^n quite 

18.  * well, not as good as MD5, but still good. 

19.  */  

20.  unsigned long lh_strhash(const char *c)  

21.  {  

22.      unsigned long ret=0;  

23.      long n;  

24.      unsigned long v;  

25.      int r;  

26.    

27.    

28.      if ((c == NULL) || (*c == '\0'))  

29.          return(ret);  

30.      /* 

31.      unsigned char b[16]; 

32.      MD5(c,strlen(c),b); 

33.      return(b[0]|(b[1]<<8)|(b[2]<<16)|(b[3]<<24)); 

34.      */  

35.    

36.    

37.      n=0x100;  

38.      while (*c)  

39.      {  

40.          v=n|(*c);  

41.          n+=0x100;  

42.          r= (int)((v>>2)^v)&0x0f;  

43.          ret=(ret(32-r));  

44.          ret&=0xFFFFFFFFL;  

45.          ret^=v*v;  

46.          c++;  

47.      }  

48.      return((ret>>16)^ret);  

49.  }  

 

[cpp] view plaincopy

1.   // MySql中出现的字符串Hash函数  

2.   #ifndef NEW_HASH_FUNCTION  

3.     

4.   /* Calc hashvalue for a key */  

5.   static uint calc_hashnr(const byte *key,uint length)  

6.   {  

7.       register uint nr=1, nr2=4;  

8.       while (length--)  

9.       {  

10.          nr^= (((nr & 63)+nr2)*((uint) (uchar) *key++))+ (nr << 8);  

11.          nr2+=3;  

12.      }  

13.      return((uint) nr);  

14.  }  

15.    

16.    

17.  /* Calc hashvalue for a key, case indepenently */  

18.  static uint calc_hashnr_caseup(const byte *key,uint length)  

19.  {  

20.      register uint nr=1, nr2=4;  

21.      while (length--)  

22.      {  

23.          nr^= (((nr & 63)+nr2)*((uint) (uchar) toupper(*key++)))+ (nr << 8);  

24.          nr2+=3;  

25.      }  

26.      return((uint) nr);  

27.  }  

28.    

29.  #else  

30.    

31.  /* 

32.  * Fowler/Noll/Vo hash 

33.  * 

34.  * The basis of the hash algorithm was taken from an idea sent by email to the 

35.  * IEEE Posix P1003.2 mailing list from Phong Vo ([email protected]) and 

36.  * Glenn Fowler ([email protected]). Landon Curt Noll ([email protected]) 

37.  * later improved on their algorithm. 

38.  * 

39.  * The magic is in the interesting relationship between the special prime 

40.  * 16777619 (2^24 + 403) and 2^32 and 2^8. 

41.  * 

42.  * This hash produces the fewest collisions of any function that we've seen so 

43.  * far, and works well on both numbers and strings. 

44.  */  

45.    

46.  uint calc_hashnr(const byte *key, uint len)  

47.  {  

48.      const byte *end=key+len;  

49.      uint hash;  

50.      for (hash = 0; key < end; key++)  

51.      {  

52.          hash *= 16777619;  

53.          hash ^= (uint) *(uchar*) key;  

54.      }  

55.      return (hash);  

56.  }  

57.    

58.  uint calc_hashnr_caseup(const byte *key, uint len)  

59.  {  

60.      const byte *end=key+len;  

61.      uint hash;  

62.      for (hash = 0; key < end; key++)  

63.      {  

64.          hash *= 16777619;  

65.          hash ^= (uint) (uchar) toupper(*key);  

66.      }  

67.      return (hash);  

68.  }  

69.  #endif  



3. Hash 表

        我们接下去分析Poco中Hash表的实现。Poco中实现了3种哈希表,分别是SimpleHashTable, HashTable,LinearHashTable。它们的实现对应了当出现冲突时,解决冲突的不同方法。首先我们看一下通用的解决方法。
        1. 线性探测。当出现碰撞时,顺序依次查询后续位置,直到找到空位。《利用线性探测法构造散列表》
        2. 双重散列法。当使用第一个散列Hash函数,出现碰撞时,用第二个散列函数去寻找空位
        3. 拉链法。出现碰撞的时候,使用list存储碰撞数据
        4. 线性哈希,linear hash。立刻分裂或者延迟分裂。通过分裂,控制桶的高度,每次分裂时,会重新散列碰撞元素。linearhashing

        SimpleHashTable的实现对应了方法一;HashTable对应了方法3;LinearHashTable对应了方法4。

3.1 SimpleHashTable

        从类图里我们看到,SimpleHashTable是一个HashEntry容器, 内部定义如下:

[cpp] view plaincopy

1.   std::vector _entries  

        当插入新数据时,首先根据hash值,计算空位,然后存储;如果发现冲突,顺着计算的hash值按地址顺序依次寻找空位;如_entries容器无空位,则抛出异常。

[cpp] view plaincopy

1.   UInt32 insert(const Key& key, const Value& value)  

2.   /// Returns the hash value of the inserted item.  

3.   /// Throws an exception if the entry was already inserted  

4.   {  

5.       UInt32 hsh = hash(key);  

6.       insertRaw(key, hsh, value);  

7.       return hsh;  

8.   }  

9.     

10.  Value& insertRaw(const Key& key, UInt32 hsh, const Value& value)  

11.  /// Returns the hash value of the inserted item.  

12.  /// Throws an exception if the entry was already inserted  

13.  {  

14.      UInt32 pos = hsh;  

15.      if (!_entries[pos])  

16.          _entries[pos] = new HashEntry(key, value);  

17.      else  

18.      {  

19.          UInt32 origHash = hsh;  

20.          while (_entries[hsh % _capacity])  

21.          {  

22.              if (_entries[hsh % _capacity]->key == key)  

23.                  throw ExistsException();  

24.              if (hsh - origHash > _capacity)  

25.                  throw PoolOverflowException("SimpleHashTable full");  

26.              hsh++;  

27.          }  

28.          pos = hsh % _capacity;  

29.          _entries[pos] = new HashEntry(key, value);  

30.      }  

31.      _size++;  

32.      return _entries[pos]->value;  

33.  }  


        SimpleHashTable进行搜索时,策略也一致。

[cpp] view plaincopy

1.   const Value& get(const Key& key) const  

2.   /// Throws an exception if the value does not exist  

3.   {  

4.       UInt32 hsh = hash(key);  

5.       return getRaw(key, hsh);  

6.   }  

7.     

8.   const Value& getRaw(const Key& key, UInt32 hsh) const  

9.   /// Throws an exception if the value does not exist  

10.  {  

11.      UInt32 origHash = hsh;  

12.      while (true)  

13.      {  

14.          if (_entries[hsh % _capacity])  

15.          {  

16.              if (_entries[hsh % _capacity]->key == key)  

17.              {  

18.                  return _entries[hsh % _capacity]->value;  

19.              }  

20.          }  

21.          else  

22.              throw InvalidArgumentException("value not found");  

23.          if (hsh - origHash > _capacity)  

24.              throw InvalidArgumentException("value not found");  

25.          hsh++;  

26.      }  

27.  }  


        SimpleHashTable没有提供删除数据的接口,只适用于数据量不大的简单应用。


3.2 HashTable

        HashTable是拉链法的一个变种。当冲突数据发生时,存储的容器是map而不是list。其内部容器定义为:

[cpp] view plaincopy

1.   HashEntryMap** _entries;  

        同map相比,它实际上是把一个大map分成了很多个小map,通过hash方法寻找到小map,再通过map的find函数寻找具体数据。其插入和搜索数据函数如下:

[cpp] view plaincopy

1.   UInt32 insert(const Key& key, const Value& value)  

2.   /// Returns the hash value of the inserted item.  

3.   /// Throws an exception if the entry was already inserted  

4.   {  

5.       UInt32 hsh = hash(key);  

6.       insertRaw(key, hsh, value);  

7.       return hsh;  

8.   }  

9.     

10.    

11.  Value& insertRaw(const Key& key, UInt32 hsh, const Value& value)  

12.  /// Returns the hash value of the inserted item.  

13.  /// Throws an exception if the entry was already inserted  

14.  {  

15.      if (!_entries[hsh])  

16.          _entries[hsh] = new HashEntryMap();  

17.      std::pair<typename HashEntryMap::iterator, bool> res(_entries[hsh]->insert(std::make_pair(key, value)));  

18.      if (!res.second)  

19.          throw InvalidArgumentException("HashTable::insert, key already exists.");  

20.      _size++;  

21.      return res.first->second;  

22.  }  

23.    

24.    

25.  const Value& get(const Key& key) const  

26.  /// Throws an exception if the value does not exist  

27.  {  

28.      UInt32 hsh = hash(key);  

29.      return getRaw(key, hsh);  

30.  }  

31.    

32.    

33.  const Value& getRaw(const Key& key, UInt32 hsh) const  

34.  /// Throws an exception if the value does not exist  

35.  {  

36.      if (!_entries[hsh])  

37.          throw InvalidArgumentException("key not found");  

38.    

39.      ConstIterator it = _entries[hsh]->find(key);  

40.      if (it == _entries[hsh]->end())  

41.          throw InvalidArgumentException("key not found");  

42.    

43.      return it->second;  

44.  }  


        HashTable支持remove操作。



3.2 LinearHashTable

        LinearHashTable按照解决冲突的方法4实现。它内部的容器为vector>,同时还存在两个控制量_split和_front:

[cpp] view plaincopy

1.   std::size_t _split;  

2.   std::size_t _front;  

3.   vector> _buckets;  


        它的插入操作如下:

[cpp] view plaincopy

1.   std::pairbool> insert(const Value& value)  

2.   /// Inserts an element into the table.  

3.   ///  

4.   /// If the element already exists in the table,  

5.   /// a pair(iterator, false) with iterator pointing to the   

6.   /// existing element is returned.  

7.   /// Otherwise, the element is inserted an a   

8.   /// pair(iterator, true) with iterator  

9.   /// pointing to the new element is returned.  

10.  {  

11.      std::size_t hash = _hash(value);  

12.      std::size_t addr = bucketAddressForHash(hash);  

13.      BucketVecIterator it(_buckets.begin() + addr);  

14.      BucketIterator buckIt(std::find(it->begin(), it->end(), value));  

15.      if (buckIt == it->end())  

16.      {  

17.          split();  

18.          addr = bucketAddressForHash(hash);  

19.          it = _buckets.begin() + addr;  

20.          buckIt = it->insert(it->end(), value);  

21.          ++_size;  

22.          return std::make_pair(Iterator(it, _buckets.end(), buckIt), true);  

23.      }  

24.      else  

25.      {  

26.          return std::make_pair(Iterator(it, _buckets.end(), buckIt), false);  

27.      }  

28.  }  


        其中split函数是所有操作的关键:

[cpp] view plaincopy

1.   void split()  

2.   {  

3.       if (_split == _front)  

4.       {  

5.           _split = 0;  

6.           _front *= 2;  

7.           _buckets.reserve(_front*2);  

8.       }  

9.       Bucket tmp;  

10.      _buckets.push_back(tmp);  

11.      _buckets[_split].swap(tmp);  

12.      ++_split;  

13.      for (BucketIterator it = tmp.begin(); it != tmp.end(); ++it)  

14.      {  

15.          using std::swap;  

16.          std::size_t addr = bucketAddress(*it);  

17.          _buckets[addr].push_back(Value());  

18.          swap(*it, _buckets[addr].back());  

19.      }  

20.  }  


        从上面的代码中我们可以看到,在每次插入新元素的时候,都会增加一个新的桶,并对桶_buckets[_split]进行重新散列;在_split == _front时,会把_buckets的容积扩大一倍。通过动态的增加桶的数量,这种方法降低了每个桶的高度,从而保证了搜索的效率。


4. HashMap和HashSet

        HashMap和HashSet是在LinearHashTable上的封装,使接口同stl::map和stl::set相类似,使用时非常的简单。下面来看一个例子:

[cpp] view plaincopy

1.   #include "Poco/HashMap.h"  

2.   int main()  

3.   {  

4.       typedef HashMap<intint> IntMap;  

5.       IntMap hm;  

6.         

7.       for (int i = 0; i < N; ++i)  

8.       {  

9.           std::pairbool> res = hm.insert(IntMap::ValueType(i, i*2));  

10.          IntMap::Iterator it = hm.find(i);  

11.      }         

12.        

13.      assert (!hm.empty());  

14.        

15.      for (int i = 0; i < N; ++i)  

16.      {  

17.          IntMap::Iterator it = hm.find(i);  

18.      }  

19.        

20.      for (int i = 0; i < N; ++i)  

21.      {  

22.          std::pairbool> res = hm.insert(IntMap::ValueType(i, 0));  

23.      }     

24.          return 0;  

25.  }     


POCO C++库学习和分析 -- Cache



1. Cache概述

        在STL::map或者STL::set中,容器的尺寸是没有上限的,数目可以不断的扩充。并且在STL的容器中,元素是不会自动过期的,除非显式的被删除。Poco的Cache可以被看成是STL中容器的一个扩充,容器中的元素会自动过期(即失效)。在Poco实现的Cache框架中,基础的过期策略有两种。一种是LRU(LastRecent Used),另外一种是基于时间的过期(Time based expiration)。在上述两种过期策略之上,还提供了两者之间的混合。

        下面是相关的类:
        1. LRUCache: 最近使用Cache。在内部维护一个Cache的最大容量M,始终只保存M个元素于Cache内部,当第M+1元素插入Cache中时,最先被放入Cache中的元素将失效。
        2. ExpireCache: 时间过期Cache。在内部统一管理失效时间T,当元素插入Cache后,超过时间T,则删除。
        3. AccessExpireCache: 时间过期Cache。同ExpireCache不同的是,当元素被访问后,重新开始计算该元素的超时时间,而不是只从元素插入时开始计时。
        4. UniqueExpireCache: 时间过期Cache。同ExpireCache不同的是,每一个元素都有自己单独的失效时间。
        5. UniqueAccessExpireCache:时间过期Cache。同AccessExpireCache不同的是,每一个元素都有自己单独的失效时间。
        6. ExpireLRUCache:时间过期和LRU策略的混合体。当时间过期和LRU任一过期条件被触发时,容器中的元素失效。
        7. AccessExpireLRUCache:时间过期和LRU策略的混合体。同ExpireLRUCache相比,当元素被访问后,重新开始计算该元素的超时时间,而不是只从元素插入时开始计时。
        8. UniqueExpireLRUCache:时间过期和LRU策略的混合体。同ExpireLRUCache相比,每一个元素都有自己单独的失效时间。
        9. UniqueAccessExpireLRUCache:时间过期和LRU策略的混合体。同UniqueExpireLRUCache相比,当元素被访问后,重新开始计算该元素的超时时间,而不是只从元素插入时开始计时。


2. Cache的内部结构

2.1 Cache类

        下面是Poco中Cache的类图:


        从类图中我们可以看到所有的Cache都有一个对应的strategy类。事实上strategy类负责快速搜索Cache中的过期元素。Cache和strategy采用了Poco中的同步事件机制(POCOC++库学习和分析 -- 通知和事件(四) )。


        让我们来看AbstractCache的定义:

[cpp] view plaincopy

1.   template <class TKey, class TValue, class TStrategy, class TMutex = FastMutex, class TEventMutex = FastMutex>   

2.   class AbstractCache  

3.       /// An AbstractCache is the interface of all caches.   

4.   {  

5.   public:  

6.       FIFOEvent<const KeyValueArgs, TEventMutex > Add;  

7.       FIFOEvent<const KeyValueArgs, TEventMutex > Update;  

8.       FIFOEvent<const TKey, TEventMutex>                         Remove;  

9.       FIFOEvent<const TKey, TEventMutex>                         Get;  

10.      FIFOEvent<const EventArgs, TEventMutex>                    Clear;  

11.    

12.      typedef std::map > DataHolder;  

13.      typedef typename DataHolder::iterator       Iterator;  

14.      typedef typename DataHolder::const_iterator ConstIterator;  

15.      typedef std::set                      KeySet;  

16.    

17.      AbstractCache()  

18.      {  

19.          initialize();  

20.      }  

21.    

22.      AbstractCache(const TStrategy& strat): _strategy(strat)  

23.      {  

24.          initialize();  

25.      }  

26.    

27.      virtual ~AbstractCache()  

28.      {  

29.          uninitialize();  

30.      }  

31.    

32.          // ...........  

33.    

34.  protected:  

35.      mutable FIFOEvent > IsValid;  

36.      mutable FIFOEvent           Replace;  

37.    

38.      void initialize()  

39.          /// Sets up event registration.  

40.      {  

41.          Add     += Delegateconst KeyValueArgs >(&_strategy, &TStrategy::onAdd);  

42.          Update  += Delegateconst KeyValueArgs >(&_strategy, &TStrategy::onUpdate);  

43.          Remove  += Delegateconst TKey>(&_strategy, &TStrategy::onRemove);  

44.          Get     += Delegateconst TKey>(&_strategy, &TStrategy::onGet);  

45.          Clear   += Delegateconst EventArgs>(&_strategy, &TStrategy::onClear);  

46.          IsValid += Delegate >(&_strategy, &TStrategy::onIsValid);  

47.          Replace += Delegate(&_strategy, &TStrategy::onReplace);  

48.      }  

49.    

50.      void uninitialize()  

51.          /// Reverts event registration.  

52.      {  

53.          Add     -= Delegateconst KeyValueArgs >(&_strategy, &TStrategy::onAdd );  

54.          Update  -= Delegateconst KeyValueArgs >(&_strategy, &TStrategy::onUpdate);  

55.          Remove  -= Delegateconst TKey>(&_strategy, &TStrategy::onRemove);  

56.          Get     -= Delegateconst TKey>(&_strategy, &TStrategy::onGet);  

57.          Clear   -= Delegateconst EventArgs>(&_strategy, &TStrategy::onClear);  

58.          IsValid -= Delegate >(&_strategy, &TStrategy::onIsValid);  

59.          Replace -= Delegate(&_strategy, &TStrategy::onReplace);  

60.      }  

61.    

62.      void doAdd(const TKey& key, const TValue& val)  

63.          /// Adds the key value pair to the cache.  

64.          /// If for the key already an entry exists, it will be overwritten.  

65.      {  

66.          Iterator it = _data.find(key);  

67.          doRemove(it);  

68.    

69.    

70.          KeyValueArgs args(key, val);  

71.          Add.notify(this, args);  

72.          _data.insert(std::make_pair(key, SharedPtr(new TValue(val))));  

73.            

74.          doReplace();  

75.      }  

76.    

77.      void doAdd(const TKey& key, SharedPtr& val)  

78.          /// Adds the key value pair to the cache.  

79.          /// If for the key already an entry exists, it will be overwritten.  

80.      {  

81.          Iterator it = _data.find(key);  

82.          doRemove(it);  

83.    

84.    

85.          KeyValueArgs args(key, *val);  

86.          Add.notify(this, args);  

87.          _data.insert(std::make_pair(key, val));  

88.            

89.          doReplace();  

90.      }  

91.    

92.      void doUpdate(const TKey& key, const TValue& val)  

93.          /// Adds the key value pair to the cache.  

94.          /// If for the key already an entry exists, it will be overwritten.  

95.      {  

96.          KeyValueArgs args(key, val);  

97.          Iterator it = _data.find(key);  

98.          if (it == _data.end())  

99.          {  

100.             Add.notify(this, args);  

101.             _data.insert(std::make_pair(key, SharedPtr(new TValue(val))));  

102.         }  

103.         else  

104.         {  

105.             Update.notify(this, args);  

106.             it->second = SharedPtr(new TValue(val));  

107.         }  

108.           

109.         doReplace();  

110.     }  

111.   

112.     void doUpdate(const TKey& key, SharedPtr& val)  

113.         /// Adds the key value pair to the cache.  

114.         /// If for the key already an entry exists, it will be overwritten.  

115.     {  

116.         KeyValueArgs args(key, *val);  

117.         Iterator it = _data.find(key);  

118.         if (it == _data.end())  

119.         {  

120.             Add.notify(this, args);  

121.             _data.insert(std::make_pair(key, val));  

122.         }  

123.         else  

124.         {  

125.             Update.notify(this, args);  

126.             it->second = val;  

127.         }  

128.           

129.         doReplace();  

130.     }  

131.   

132.     void doRemove(Iterator it)   

133.         /// Removes an entry from the cache. If the entry is not found  

134.         /// the remove is ignored.  

135.     {  

136.         if (it != _data.end())  

137.         {  

138.             Remove.notify(this, it->first);  

139.             _data.erase(it);  

140.         }  

141.     }  

142.   

143.     bool doHas(const TKey& key) const  

144.         /// Returns true if the cache contains a value for the key  

145.     {  

146.         // ask the strategy if the key is valid  

147.         ConstIterator it = _data.find(key);  

148.         bool result = false;  

149.   

150.   

151.         if (it != _data.end())  

152.         {  

153.             ValidArgs args(key);  

154.             IsValid.notify(this, args);  

155.             result = args.isValid();  

156.         }  

157.   

158.         return result;  

159.     }  

160.   

161.     SharedPtr doGet(const TKey& key)   

162.         /// Returns a SharedPtr of the cache entry, returns 0 if for  

163.         /// the key no value was found  

164.     {  

165.         Iterator it = _data.find(key);  

166.         SharedPtr result;  

167.   

168.         if (it != _data.end())  

169.         {     

170.             // inform all strategies that a read-access to an element happens  

171.             Get.notify(this, key);  

172.             // ask all strategies if the key is valid  

173.             ValidArgs args(key);  

174.             IsValid.notify(this, args);  

175.   

176.             if (!args.isValid())  

177.             {  

178.                 doRemove(it);  

179.             }  

180.             else  

181.             {  

182.                 result = it->second;  

183.             }  

184.         }  

185.   

186.         return result;  

187.     }  

188.   

189.     void doClear()  

190.     {  

191.         static EventArgs _emptyArgs;  

192.         Clear.notify(this, _emptyArgs);  

193.         _data.clear();  

194.     }  

195.   

196.     void doReplace()  

197.     {  

198.         std::set delMe;  

199.         Replace.notify(this, delMe);  

200.         // delMe contains the to be removed elements  

201.         typename std::set::const_iterator it    = delMe.begin();  

202.         typename std::set::const_iterator endIt = delMe.end();  

203.   

204.         for (; it != endIt; ++it)  

205.         {  

206.             Iterator itH = _data.find(*it);  

207.             doRemove(itH);  

208.         }  

209.     }  

210.   

211.     TStrategy          _strategy;  

212.     mutable DataHolder _data;  

213.     mutable TMutex  _mutex;  

214.   

215. private:  

216.     // ....  

217. };  


        从上面的定义中,可以看到AbstractCache是一个value的容器,采用map保存数据,

[cpp] view plaincopy

1.   mutable std::map > _data;  

        另外AbstractCache中还定义了一个TStrategy对象,

[cpp] view plaincopy

1.   TStrategy          _strategy;  

        并且在AbstractCache的initialize()函数中,把Cache的一些函数操作委托给TStrategy对象。其函数操作接口为:
        1. Add : 向容器中添加元素
        2. Update : 更新容器中元素
        3. Remove : 删除容器中元素
        4. Get : 获取容器中元素
        5. Clear : 清除容器中所有元素
        6. IsValid: 容器中是否某元素
        7. Replace: 按照策略从strategy中获取过期元素,并从Cache和Strategy中同时删除。将触发一系列的Remove函数。

        这几个操作中最复杂的是Add操作,其中包括了Remove、Insert和Replace操作。

[cpp] view plaincopy

1.   void doAdd(const TKey& key, SharedPtr& val)  

2.       /// Adds the key value pair to the cache.  

3.       /// If for the key already an entry exists, it will be overwritten.  

4.   {  

5.       Iterator it = _data.find(key);  

6.       doRemove(it);  

7.     

8.     

9.       KeyValueArgs args(key, *val);  

10.      Add.notify(this, args);  

11.      _data.insert(std::make_pair(key, val));  

12.            

13.      doReplace();  

14.  }  



        而Replace操作可被Add、Update、Get操作触发。这是因为Cache并不是一个主动对象(POCO C++库学习和分析 -- 线程(四)),不会自动的把元素标志为失效,需要外界也就是调用方触发进行。

        在Cache类中另外一个值得注意的地方是,保存的是TValue的SharedPtr。之所以这么设计,是为了线程安全,由于replace操作可能被多个线程调用,所以解决的方法,要么是返回TValue的SharedPtr,要么是返回TValue的拷贝。同拷贝方法相比,SharedPtr的方法要更加廉价。



2.2 Strategy类

       Strategy类完成了对_data中保存的pair中key的排序工作。每个Strategy中都存在一个key的容器,其中LRUStrategy中是std::list,ExpireStrategy、UniqueAccessExpireStrategy、UniqueExpireStrategy中是std::multimap

       对于LRU策略,这么设计我是可以理解的。每次访问都会使key被重置于list的最前端。为了实现对list快速访问,增加一个std::map容器,每次对list容器进行插入操作时,把插入位的itorator保存入map中,这样对于list的访问效率可以从O(n)变成O(log(n)),因为不需要遍历了。下面是相关的代码:

[cpp] view plaincopy

1.   void onReplace(const void*, std::set& elemsToRemove)  

2.   {  

3.       // Note: replace only informs the cache which elements  

4.       // it would like to remove!  

5.       // it does not remove them on its own!  

6.       std::size_t curSize = _keyIndex.size();  

7.     

8.       if (curSize < _size)  

9.       {  

10.          return;  

11.      }  

12.    

13.      std::size_t diff = curSize - _size;  

14.      Iterator it = --_keys.end(); //--keys can never be invoked on an empty list due to the minSize==1 requirement of LRU  

15.      std::size_t i = 0;  

16.    

17.      while (i++ < diff)   

18.      {  

19.          elemsToRemove.insert(*it);  

20.          if (it != _keys.begin())  

21.          {  

22.              --it;  

23.          }  

24.      }  

25.  }  


        LRUStrategy的replace操作是,只在curSize超过设定的访问上限_size时触发,把list容器中排在末尾的(curSize-_size)个元素标志为失效。

        而对于Time base expired策略,还如此设计,我觉得不太合适。在时间策略的strategy类中,存在着两个容器,一个是std::map,另外一个是std::multimap。进行插入操作时,代码为:

[cpp] view plaincopy

1.   void onAdd(const void*, const KeyValueArgs & args)  

2.   {  

3.       Timestamp now;  

4.       IndexIterator it = _keyIndex.insert(typename TimeIndex::value_type(now, args.key()));  

5.       std::pairbool> stat = _keys.insert(typename Keys::value_type(args.key(), it));  

6.       if (!stat.second)  

7.       {  

8.           _keyIndex.erase(stat.first->second);  

9.           stat.first->second = it;  

10.      }  

11.  }  



        可以看到map容器中保存的是multimap中pair对的itorator。其replace操作如下:

[cpp] view plaincopy

1.   void onReplace(const void*, std::set& elemsToRemove)  

2.   {  

3.       // Note: replace only informs the cache which elements  

4.       // it would like to remove!  

5.       // it does not remove them on its own!  

6.       IndexIterator it = _keyIndex.begin();  

7.       while (it != _keyIndex.end() && it->first.isElapsed(_expireTime))  

8.       {  

9.           elemsToRemove.insert(it->second);  

10.          ++it;  

11.      }  

12.  }  

        可以看到这是对multimap的遍历,效率为O(n)。

        如果这样的话,我觉得完全可以把std::map和std::multimap合二为一,定义成为std::map,replace的操作仍然采用遍历,效率为O(n).
        对于基于时间的策略,O(n)的效率可能不能接受。我觉得可能的解决方法有两种。第一,把Cache变成主动对象,内部定期的收集失效元素,而不由外部触发。这样虽然并没有提高replace操作效率,但把replace操作和外部接口的add等操作分开了。外部调用接口的效率提高了。第二,在内部实现多个map容器,分组管理不同过期时间的对象。



3. 开销

        Poco中的Cache类比std::map要慢,其中开销最大的操作为add操作。采用Time Expire策略的Cache要比采用LRU策略的Cache更慢。并且由于Cache类引入了SharePtr和Strategy,其空间花费也要大于std::map。所以在没有必要使用Cache的情况下,还是使用map较好。



4. 例子

        下面是Cache的一个示例:

[cpp] view plaincopy

1.   #include "Poco/LRUCache.h"  

2.   int main()  

3.   {  

4.       Poco::LRUCache<int, std::string> myCache(3);  

5.       myCache.add(1, "Lousy"); // |-1-| -> first elem is the most popular one  

6.       Poco::SharedPtr ptrElem = myCache.get(1); // |-1-|  

7.       myCache.add(2, "Morning"); // |-2-1-|  

8.       myCache.add(3, "USA"); // |-3-2-1-|  

9.       // now get rid of the most unpopular entry: "Lousy"  

10.      myCache.add(4, "Good"); // |-4-3-2-|  

11.      poco_assert (*ptrElem == "Lousy"); // content of ptrElem is still valid  

12.      ptrElem = myCache.get(2); // |-2-4-3-|  

13.      // replace the morning entry with evening  

14.      myCache.add(2, "Evening"); // 2 Events: Remove followed by Add  

15.  }  


POCO C++库学习和分析 -- 字符编码

 

1. 字符编码

1.1 字符编码的概念

        字符编码可以理解为在计算机上语言符号和二比特数之间的映射。不同的编码方式对应着不同映射方法,对于映射集的双方而言,用一种映射方法下,映射关系是一一对应的。由于语言的基本符号是有限的,所以作为映射的双方,映射集也是有限的。下面这段概念的介绍来自于文章《字符编码:Unicode/UTF-8/UTF-16/UCS/Endian/BMP/BOM》、《C++字符串完全指引(ZT)》、《字符编码笔记:ASCIIUnicodeUTF-8》,并混杂了一些自己的理解。



1. ASCII码 

        对于英文字母而言,语言的符号是26个字母,因此在上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。ASCII码一共规定了128个字符的编码,比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。 


2. 非ASCII编码 

        英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。 
        但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0—127表示的符号是一样的,不一样的只是128—255的这一段。 
        至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256×256=65536个符号。中文编码的问题需要专文讨论,这篇笔记不涉及。这里只指出,虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。 

 

3. 各自定义非ASCII编码的问题

        由于各国都制定了自己的兼容ascii编码规范,就是各种ANSI码,比如我国的gb2312,用两个扩展ascii字符来表示一个中文。这带来了一个新问题,这些ansi码无法同时存在,因为它们的定义互相重叠,要自由使用不同语言就必须有一个新编码,为各种文字统一分配编码。 

 

4. 微软的解决方案

        微软为了解决这一问题,提出了一个自己的解决方案。windows上的MBCS方法,在 MBCS 下,字符被编码为单字节或双字节。在双字节字符中,第一个字节(即前导字节)表示它和下一个字节将被解释为一个字符。第一个字节来自留作前导字节的代码范围。哪个范围的字节可以用作前导字节取决于所使用的代码页。例如,日文代码页 932 使用 0x81 到 0x9F 范围内的字节作为前导字节,而朝鲜语代码页 949 则使用其他范围的字节。

 

5. 另一种解决方案Unicode

        虽然微软提了自己的方案,其他人也没闲着。为了解决这一问题。国际标准化组织(ISO)想出了一个办法,这个办法其实和微软也类似。即存在有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。 
        Unicode是一种字符编码方法,它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是”UniversalMultiple-Octet Coded Character Set
”,简称为UCS。UCS可以看作是”Unicode Character Set”的缩写。 

       Unicode现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字“严”。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。 
        根据维基百科的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。 在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。 目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。 

 

6. UCS-2、UCS-4、BMP 

        UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏: 
        UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。 
        UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。 
        group 0的plane 0被称作BasicMultilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。 
        将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。
        由于即使是老UCS-2,也可以表示2^16=65535个字符,基本上可以容纳所有常用各国字符,所以目前各国基本都使用UCS-2。  


7. Unicode的问题 

        值得注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。 
        比如,汉字“严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。 
        这里就有两个严重的问题,第一个问题是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。 
        它们造成的结果是:1)出现了unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示unicode。2)unicode在很长一段时间内无法推广,直到互联网的出现。 
        怎样存储和传输这些编码,是由UTF(UCS TransformationFormat)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16、UTF-32。


8. UTF编码

        UTF(UCS Transformation Format)规范设计时考虑了一些现实问题。即在UCS定义之前,已经存在大量的ASCII程序。新定义的UCS的表示方法必须兼容原始的ascii程序和方法。
        这个问题也可以表示为,Unicode使用2个字节表示一个字符,ascii使用1个字节,在很多方面产生了冲突,以前处理ascii的方法都必须重写。而且C语言用\0作为字符串结束标志,但Unicode中很多字符都含\0,C语言的字符串函数也无法正常处理Unicode。为了把unicode投入实用,出现了UTF,最常见的是UTF-8、UTF-16和UTF-32。

        其中UTF-16和Unicode本身的编码是一致的,UTF-32和UCS-4也是相同的,但最重要的是UTF-8编码方式。(UTF-32中字符的数量为2^32,也就是说用一个4 byte的int值可以表示一个人类字符。一个int值既然可以可以表示所有UCS-4中的字符,当然也可以表示UCS-2中对应的所有字符)。那为什么会出现UTF-8编码方式呢。UTF8是一种变长的编码,它的字节数是不固定的,使用第一个字节确定字节数。第一个字节首为0即一个字节,110即2字节,1110即3字节,字符后续字节都用10开始,这样不会混淆且单字节英文字符可仍用ASCII编码。理论上UTF-8最大可以用6字节表示一个字符,但Unicode目前没有用大于0xffff的字符,实际UTF-8最多使用了3个字节。 

        UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下: 


UCS-2编码(16进制)   

bit数

UTF-8 字节流(二进制)   

byte数

备注

0000 0000 ~

0000 007F

0~7

0XXX XXXX

1

0000 0080 ~

0000 07FF

8~11

110X XXXX

10XX XXXX

2

0000 0800 ~

0000 FFFF

12~16

1110 XXXX

10XX XXXX

10XX XXXX

3

基本定义范围:0~FFFF

0001 0000 ~

001F FFFF

17~21

1111 0XXX

10XX XXXX

10XX XXXX

10XX XXXX

4

Unicode6.1定义范围:0~10 FFFF

0020 0000 ~

03FF FFFF

22~26

1111 10XX

10XX XXXX

10XX XXXX

10XX XXXX

10XX XXXX

5

0400 0000 ~

7FFF FFFF

27~31

1111 110X

10XX XXXX

10XX XXXX

10XX XXXX

10XX XXXX

10XX XXXX

6

表一,UCS-2到UTF-8的编码方式表


        例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx10xxxxxx 10xxxxxx。将6C49写成二进制是:011011000100 1001,用这个比特流依次代替模板中的x,得到:111001101011000110001001,即E6 B1 89。 
        读者可以用记事本测试一下我们的编码是否正确。 
        UTF-16以16位为单元对UCS进行编码。对于小于0×10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0×10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0×10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。 

        看到这里,读者要问了,对于汉字来说,使用UTF-8来说,存储的字节数"E6 B189"要比直接使用Unicode编码"6C49"还多啊。没办法,对于汉字来说,确实增多了。但对于英语系国家来说,UTF-8比Unicode省了。谁叫计算机是别们发明的呢,总是有点特权的。

 

9. Little endian和Big endian

        上一节已经提到,Unicode码可以采用UCS-2格式直接存储。以汉字”严“为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。 
        这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。 因此,第一个字节在前,就是”大头方式“(Big endian),第二个字节在前就是”小头方式“(Little endian)。 那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码? 
        Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格“(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。 
        如果一个文本文件的头两个字节是FEFF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。 


 

2. Poco中字符编码

2.1 编码的介绍

        有了上面的基本概念,对于Poco中的字符编码,理解就简单了。在Poco中存在ASCII,Latin1,Latin9,Windows1252,UTF16,UTF8编码。其中ASCII对应的字符集大小为128;Latin1和Latin9表达的对象为拉丁语,其对应的字符集大小为256;Windows1252对应的字符集大小也为256;UTF16,UTF8表达的字符集对象为UCS2,大小为2^32。下面把涉及的这几种编码说的详细一点:

       Latin1:Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。

  ISO-8859-1
  ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。
  ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在ISO-8859-1当中。
  因为ISO-8859-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin1就是利用了这个特性。ASCII编码是一个7位的容器,ISO-8859-1编码是一个8位的容器。
       Latin9:

       看代码的话,和Latin1的区别在于,Latin1用2个字节去表示文字符号,而Latin9用4个字节表示文字符号。

       UTF8

       UTF-8是UNICODE的一种变长字符编码又称万国码,由KenThompson于1992年创建。现在已经标准化为RFC 3629。UTF-8用1到6个字节编码UNICODE字符。用在网页上可以同一页面显示中文简体繁体及其它语言(如日文,韩文)。UTF-8编码的优点,UTF-8编码可以通过屏蔽位和移位操作快速读写。字符串比较时strcmp()和wcscmp()的返回结果相同,因此使排序变得更加容易。字节FF和FE在UTF-8编码中永远不会出现,因此他们可以用来表明UTF-16或UTF-32文本(见BOM) UTF-8 是字节顺序无关的。它的字节顺序在所有系统中都是一样的,因此它实际上并不需要BOM。UTF-8编码的缺点,无法从UNICODE字符数判断出UTF-8文本的字节数,因为UTF-8是一种变长编码它需要用2个字节编码那些用扩展ASCII字符集只需1个字节的字符 ISO Latin-1 是UNICODE的子集,但不是UTF-8的子集 8位字符的UTF-8编码会被email网关过滤,因为internet信息最初设计为7位ASCII码。因此产生了UTF-7编码。 UTF-8 在它的表示中使用值100xxxxx的几率超过50%, 而现存的实现如ISO 2022, 4873, 6429, 和8859系统,会把它错认为是C1 控制码。因此产生了UTF-7.5编码。

       UTF16

       UTF-16是Unicode的其中一个使用方式。 UTF是 Unicode Translation Format,即把Unicode转做某种格式的意思。它定义于ISO/IEC 10646-1的附录Q,而RFC2781也定义了相似的做法。在Unicode基本多文种平面定义的字符(无论是拉丁字母、汉字或其他文字或符号),一律使用2字节储存。而在辅助平面定义的字符,会以代理对(surrogate pair)的形式,以两个2字节的值来储存。UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节 (2字节) 储存,但UTF-16却无法兼容于ASCII编码。c#中默认的就是UTF-16,所以在处理c#字符串的时候只能是byte,stream等方式去处理。

       UTF-32
       UTF-32 (或 UCS-4)是一种将Unicode字符编码的协定,对每一个Unicode码位使用恰好32位元。其它的Unicode transformation formats则使用不定长度编码。因为UTF-32对每个字符都使用4字节,就空间而言,是非常没有效率的。特别地,非基本多文种平面的字符在大部分文件中通常很罕见,以致于它们通常被认为不存在占用空间大小的讨论,使得UTF-32通常会是其它编码的二到四倍。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。与UTF-8及UTF-16相比,它有点更容易遭截断。
       

2.2 字符原集和表示之间的转换类

       Poco中的编码类都从TextEncoding类继承。TextEncoding类的接口定义如下:

[cpp] view plaincopy

1.   class Foundation_API TextEncoding  

2.   {  

3.   public:  

4.       typedef SharedPtr Ptr;  

5.         

6.       enum  

7.       {  

8.           MAX_SEQUENCE_LENGTH = 6 /// The maximum character byte sequence length supported.  

9.       };  

10.        

11.      typedef int CharacterMap[256];  

12.    

13.    

14.      virtual ~TextEncoding();  

15.    

16.      virtual const char* canonicalName() const = 0;  

17.    

18.      virtual bool isA(const std::string& encodingName) const = 0;  

19.                

20.                 // ........  

21.            

22.      virtual int convert(const unsigned char* bytes) const;  

23.    

24.      virtual int queryConvert(const unsigned char* bytes, int length) const;  

25.    

26.      virtual int sequenceLength(const unsigned char* bytes, int length) const;  

27.    

28.      virtual int convert(int ch, unsigned char* bytes, int length) const;  

29.    

30.    

31.                 // ....  

32.            

33.  protected:  

34.      static TextEncodingManager& manager();  

35.          /// Returns the TextEncodingManager.  

36.  };  

       我们可以把要表示的字符集称为字符原集,如UCS2,UCS4,它规定了字符集中存在哪些字符,并把每一个字符和数字之间建立一一映射关系。由于字符原集是个有穷集合,一个int值(2^32)足以表示其定义。一个字符的原集在被应用到计算机中时,会存在多种表示方式,如UCS2可以表示为UTF8,UTF16,我们称为原集的表示。

      TextEncoding中下面两个函数,用来把”原集的表示“转换为原集字符(一个int值)。

[cpp] view plaincopy

1.   int convert(const unsigned char* bytes) const;  

2.   int queryConvert(const unsigned char* bytes, int length) const;  

       而下面这个函数则用来把原集字符(一个int值)转换成”原集的表示“。

[cpp] view plaincopy

1.   int convert(int ch, unsigned char* bytes, int length) const;  

      拿UTF8Encoding类来举例,其原集为UCS2,表示方法是UTF8。

      ”原集字符“转成”UTF8“表示,其函数实现如下:

[cpp] view plaincopy

1.   int UTF8Encoding::convert(int ch, unsigned char* bytes, int length) const  

2.   {  

3.       if (ch <= 0x7F)  

4.       {  

5.           if (bytes && length >= 1)  

6.               *bytes = (unsigned char) ch;  

7.           return 1;  

8.       }  

9.       else if (ch <= 0x7FF)  

10.      {  

11.          if (bytes && length >= 2)  

12.          {  

13.              *bytes++ = (unsigned char) (((ch >> 6) & 0x1F) | 0xC0);  

14.              *bytes   = (unsigned char) ((ch & 0x3F) | 0x80);  

15.          }  

16.          return 2;  

17.      }  

18.      else if (ch <= 0xFFFF)  

19.      {  

20.          if (bytes && length >= 3)  

21.          {  

22.              *bytes++ = (unsigned char) (((ch >> 12) & 0x0F) | 0xE0);  

23.              *bytes++ = (unsigned char) (((ch >> 6) & 0x3F) | 0x80);  

24.              *bytes   = (unsigned char) ((ch & 0x3F) | 0x80);  

25.          }  

26.          return 3;  

27.      }  

28.      else if (ch <= 0x10FFFF)  

29.      {  

30.          if (bytes && length >= 4)  

31.          {  

32.              *bytes++ = (unsigned char) (((ch >> 18) & 0x07) | 0xF0);  

33.              *bytes++ = (unsigned char) (((ch >> 12) & 0x3F) | 0x80);  

34.              *bytes++ = (unsigned char) (((ch >> 6) & 0x3F) | 0x80);  

35.              *bytes   = (unsigned char) ((ch & 0x3F) | 0x80);  

36.          }  

37.          return 4;  

38.      }  

39.      else return 0;  

40.  }  

     ”UTF8“表示转成”原集字符“,其函数实现如下:

[cpp] view plaincopy

1.   int UTF8Encoding::convert(const unsigned char* bytes) const  

2.   {  

3.       int n = _charMap[*bytes];  

4.       int uc;  

5.         

6.       switch (n)  

7.       {  

8.       case -6:  

9.       case -5:  

10.      case -1:  

11.          return -1;  

12.      case -4:   

13.      case -3:   

14.      case -2:  

15.          if (!isLegal(bytes, -n)) return -1;  

16.          uc = *bytes & ((0x07 << (n + 4)) | 0x03);  

17.          break;  

18.      default:  

19.          return n;  

20.      }  

21.    

22.      while (n++ < -1)   

23.      {     

24.          uc <<= 6;  

25.          uc |= (*++bytes & 0x3F);  

26.      }  

27.      return uc;  

28.  }  

     这两段代码就是上面的表一《UCS-2到UTF-8的编码方式表》的代码表现。其他的编码也类似。在UTF16Encoding类中,由于UCS2和UTF16表示是一致的,所以不存在转换关系,但有bigendian和litterendian实现问题。在ASCIIEncoding类中,原集和其表现也一致,所以也不存在转换问题。

     下面是  TextEncoding和其相关类的类图:



     TextEncodingManager是TextEncoding类的工厂类,创建了ASCIIEncoding、UTF16Encoding等编码对象。

2.3 字符集之间的转换

     不同字符集之间的转换,实际上是不同字符原集的不同表示之间的转换。如果两个表示方法的原集相同,转换起来自然方便一些。Poco中提供了UnicodeConverter类用于UTF8和UTF16之间的转换。其定义如下:

[cpp] view plaincopy

1.   class Foundation_API UnicodeConverter  

2.   {  

3.   public:  

4.       static void toUTF16(const std::string& utf8String, std::wstring& utf16String);  

5.           /// Converts the given UTF-8 encoded string into an UTF-16 encoded wstring.  

6.     

7.       static void toUTF16(const char* utf8String, int length, std::wstring& utf16String);   

8.           /// Converts the given UTF-8 encoded character sequence into an UTF-16 encoded string.  

9.     

10.      static void toUTF16(const char* utf8String, std::wstring& utf16String);   

11.          /// Converts the given zero-terminated UTF-8 encoded character sequence into an UTF-16 encoded wstring.  

12.    

13.      static void toUTF8(const std::wstring& utf16String, std::string& utf8String);  

14.          /// Converts the given UTF-16 encoded wstring into an UTF-8 encoded string.  

15.    

16.      static void toUTF8(const wchar_t* utf16String, int length, std::string& utf8String);  

17.          /// Converts the given zero-terminated UTF-16 encoded wide character sequence into an UTF-8 encoded wstring.  

18.    

19.      static void toUTF8(const wchar_t* utf16String, std::string& utf8String);  

20.          /// Converts the given UTF-16 encoded zero terminated character sequence into an UTF-8 encoded string.  

21.  };  

     注意UTF-16用在C++中是用wtring存储的。虽然UTF-16对应着UCS2,内部存储时,一个short已经足够。但在Linux下默认是占4个字节,当然在用GCC编译时可以使用-fshort-wchar来强制使用2个字节,而在Windows上被定义为unsigned short。


     如果两个表示方法的原集不同,则要考虑转换方向问题。比如说中文字符在ASCII码中不存在,那么毫无疑问,把中文字符转换成ASCII码自然无意义,这个方向的转换注定要失败。在Poco中,上述字符集之间的转换是用类TextConverter来实现的。下面是它的定义:

[cpp] view plaincopy

1.   class Foundation_API TextConverter  

2.       /// A TextConverter converts strings from one encoding  

3.       /// into another.  

4.   {  

5.   public:  

6.       typedef int (*Transform)(int);  

7.           /// Transform function for convert.  

8.             

9.       TextConverter(const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?');  

10.          /// Creates the TextConverter. The encoding objects must not be deleted while the  

11.          /// TextConverter is in use.  

12.    

13.      ~TextConverter();  

14.          /// Destroys the TextConverter.  

15.            

16.      int convert(const std::string& source, std::string& destination, Transform trans);  

17.          /// Converts the source string from inEncoding to outEncoding  

18.          /// and appends the result to destination. Every character is  

19.          /// passed to the transform function.  

20.          /// If a character cannot be represented in outEncoding, defaultChar  

21.          /// is used instead.  

22.          /// Returns the number of encoding errors (invalid byte sequences  

23.          /// in source).  

24.    

25.      int convert(const void* source, int length, std::string& destination, Transform trans);  

26.          /// Converts the source buffer from inEncoding to outEncoding  

27.          /// and appends the result to destination. Every character is  

28.          /// passed to the transform function.  

29.          /// If a character cannot be represented in outEncoding, defaultChar  

30.          /// is used instead.  

31.          /// Returns the number of encoding errors (invalid byte sequences  

32.          /// in source).  

33.    

34.      int convert(const std::string& source, std::string& destination);  

35.          /// Converts the source string from inEncoding to outEncoding  

36.          /// and appends the result to destination.  

37.          /// If a character cannot be represented in outEncoding, defaultChar  

38.          /// is used instead.  

39.          /// Returns the number of encoding errors (invalid byte sequences  

40.          /// in source).  

41.    

42.      int convert(const void* source, int length, std::string& destination);  

43.          /// Converts the source buffer from inEncoding to outEncoding  

44.          /// and appends the result to destination.  

45.          /// If a character cannot be represented in outEncoding, defaultChar  

46.          /// is used instead.  

47.          /// Returns the number of encoding errors (invalid byte sequences  

48.          /// in source).  

49.    

50.  private:  

51.      TextConverter();  

52.      TextConverter(const TextConverter&);  

53.      TextConverter& operator = (const TextConverter&);  

54.    

55.      const TextEncoding& _inEncoding;  

56.      const TextEncoding& _outEncoding;  

57.      int                 _defaultChar;  

58.  };  

 

       如果要在流输出之前,进行字符集转换,Poco还提供了类StreamConverterBuf。其定义为:

[cpp] view plaincopy

1.   class Foundation_API StreamConverterBuf: public UnbufferedStreamBuf  

2.       /// A StreamConverter converts streams from one encoding (inEncoding)  

3.       /// into another (outEncoding).  

4.       /// If a character cannot be represented in outEncoding, defaultChar  

5.       /// is used instead.  

6.       /// If a byte sequence is not valid in inEncoding, defaultChar is used  

7.       /// instead and the encoding error count is incremented.  

8.   {  

9.   public:  

10.      StreamConverterBuf(std::istream& istr, const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?');  

11.          /// Creates the StreamConverterBuf and connects it  

12.          /// to the given input stream.  

13.    

14.      StreamConverterBuf(std::ostream& ostr, const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?');  

15.          /// Creates the StreamConverterBuf and connects it  

16.          /// to the given output stream.  

17.    

18.      ~StreamConverterBuf();  

19.          /// Destroys the StreamConverterBuf.  

20.    

21.      int errors() const;  

22.          /// Returns the number of encoding errors encountered.  

23.    

24.  protected:  

25.      int readFromDevice();  

26.      int writeToDevice(char c);  

27.    

28.  private:  

29.      std::istream*       _pIstr;  

30.      std::ostream*       _pOstr;  

31.      const TextEncoding& _inEncoding;  

32.      const TextEncoding& _outEncoding;  

33.      int                 _defaultChar;  

34.      unsigned char       _buffer[TextEncoding::MAX_SEQUENCE_LENGTH];  

35.      int                 _sequenceLength;  

36.      int                 _pos;  

37.      int                 _errors;  

38.  };  




2.4 迭代子

     TextBufferIterator和TextIterator实现了对流和字符串进行迭代。其使用大致如下:

[cpp] view plaincopy

1.   UTF8Encoding utf8Encoding;  

2.   char buffer[] = "...";  

3.   TextBufferIterator it(buffer, utf8Encoding);  

4.   TextBufferIterator end(it.end());  

5.   int n = 0;  

6.   while (it != end) { ++n; ++it; }  

      或:

[cpp] view plaincopy

1.   UTF8Encoding utf8Encoding;  

2.   std::string utf8String("....");  

3.   TextIterator it(utf8String, utf8Encoding);  

4.   TextIterator end(utf8String);  

5.   int n = 0;  

6.   while (it != end) { ++n; ++it; }  



      下面是一个完整的例子:

[cpp] view plaincopy

1.   #include "Poco/TextIterator.h"  

2.   #include "Poco/UTF8Encoding.h"  

3.   using Poco::TextIterator;  

4.   using Poco::UTF8Encoding;  

5.   int main(int argc, char** argv)  

6.   {  

7.       std::string utf8String("This is UTF-8 encoded text.");  

8.       UTF8Encoding utf8;  

9.       TextIterator it(utf8String, utf8);  

10.      TextIterator end(utf8String);  

11.      for (; it != end; ++it)  

12.      {  

13.          int unicode = *it;  

14.      }  

15.      return 0;  

16.  }  




2.5 其他

      关于编码的其他类还包括了类UTF8和类Unicode。类UTF8实现了UTF8的字符大小转换和比较,当然中文是没有大小的,大小转换只是指英文字符。而类Unicode则可以判断字符是否是Unicode原集中定义的数字,字母等。

 

2.6 例子

[cpp] view plaincopy

1.   // TextTest.cpp : Defines the entry point for the console application.  

2.   //  

3.     

4.   #include "stdafx.h"  

5.   #include "Poco/TextConverter.h"  

6.   #include "Poco/Latin1Encoding.h"  

7.   #include "Poco/UTF8Encoding.h"  

8.   #include "Poco/UTF16Encoding.h"  

9.   #include "Poco/UTF8String.h"  

10.  #include "Poco/TextIterator.h"  

11.  #include "Poco/UTF8Encoding.h"  

12.  #include   

13.  #include   

14.    

15.  using Poco::TextConverter;  

16.  using Poco::Latin1Encoding;  

17.  using Poco::UTF8Encoding;  

18.  using Poco::UTF16Encoding;  

19.  using Poco::UTF8;  

20.  using Poco::TextIterator;  

21.  using Poco::UTF8Encoding;  

22.    

23.  #include "Poco/StreamConverter.h"  

24.    

25.  using Poco::OutputStreamConverter;  

26.    

27.  void TestConvert()  

28.  {  

29.      std::string latin1String("This is Latin-1 encoded text.");  

30.      std::string utf8String;  

31.      Latin1Encoding latin1;  

32.      UTF8Encoding utf8;  

33.      TextConverter converter(latin1, utf8);  

34.      converter.convert(latin1String, utf8String);  

35.      std::cout << utf8String << std::endl;  

36.    

37.      std::string latin1StringZ("中国.");  

38.      std::string utf8StringZ;  

39.      UTF16Encoding utf16;  

40.      UTF8Encoding utf8Z;  

41.      TextConverter converterZ(utf16, utf8Z);  

42.      converterZ.convert(latin1StringZ, utf8StringZ);  

43.      std::cout << utf8StringZ << std::endl;  

44.  }  

45.    

46.  void TestStream()  

47.  {  

48.      std::string latin1String("This is Latin-1 encoded text.");  

49.      Latin1Encoding latin1;  

50.      UTF8Encoding utf8;  

51.      OutputStreamConverter converter(std::cout, latin1, utf8);  

52.      converter << latin1String << std::endl; // console output will be UTF-8  

53.  }  

54.    

55.  void TestUTF8()  

56.  {  

57.      std::string s3("\303\274\303\266\303\244"); // "u"o"a  

58.      UTF8::toUpperInPlace(s3);     

59.      assert (s3 == "\303\234\303\226\303\204"); // "U"O"A  

60.      UTF8::toLowerInPlace(s3);  

61.      assert (s3 == "\303\274\303\266\303\244"); // "u"o"a  

62.  }  

63.    

64.  void TestIterator()  

65.  {  

66.      std::string utf8String("This is UTF-8 encoded text.");  

67.      UTF8Encoding utf8;  

68.      TextIterator it(utf8String, utf8);  

69.      TextIterator end(utf8String);  

70.      int unicode;  

71.      for (; it != end; ++it)  

72.      {  

73.          unicode = *it;  

74.      }  

75.  }  

76.    

77.  int _tmain(int argc, _TCHAR* argv[])  

78.  {  

79.      TestLatinToUtf8();  

80.      TestStream();  

81.      TestUTF8();  

82.      TestIterator();  

83.      return 0;  

84.  }  

 

 

POCO C++库学习和分析 -- 平台与环境

 

         在写程序的时候,有时候需要收集一些系统信息,用作软硬件的绑定或生成唯一的注册码信息等。Poco中提供了一个很简单的类Environment来实现这个功能。这个类的定义如下:

 

1.   class Foundation_API Environment  

2.       /// This class provides access to environment variables  

3.       /// and some general system information.  

4.   {  

5.   public:  

6.       typedef UInt8 NodeId[6]; /// Ethernet address.  

7.         

8.       static std::string get(const std::string& name);  

9.           /// Returns the value of the environment variable  

10.          /// with the given name. Throws a NotFoundException  

11.          /// if the variable does not exist.  

12.            

13.      static std::string get(const std::string& name, const std::string& defaultValue);  

14.          /// Returns the value of the environment variable  

15.          /// with the given name. If the environment variable  

16.          /// is undefined, returns defaultValue instead.  

17.            

18.      static bool has(const std::string& name);  

19.          /// Returns true iff an environment variable  

20.          /// with the given name is defined.  

21.            

22.      static void set(const std::string& name, const std::string& value);  

23.          /// Sets the environment variable with the given name  

24.          /// to the given value.  

25.    

26.      static std::string osName();  

27.          /// Returns the operating system name.  

28.            

29.      static std::string osVersion();  

30.          /// Returns the operating system version.  

31.            

32.      static std::string osArchitecture();  

33.          /// Returns the operating system architecture.  

34.            

35.      static std::string nodeName();  

36.          /// Returns the node (or host) name.  

37.            

38.      static void nodeId(NodeId& id);  

39.          /// Returns the Ethernet address of the first Ethernet  

40.          /// adapter found on the system.  

41.          ///  

42.          /// Throws a SystemException if no Ethernet adapter is available.  

43.            

44.      static std::string nodeId();  

45.          /// Returns the Ethernet address (format "xx:xx:xx:xx:xx:xx")  

46.          /// of the first Ethernet adapter found on the system.  

47.          ///  

48.          /// Throws a SystemException if no Ethernet adapter is available.  

49.            

50.      static unsigned processorCount();  

51.          /// Returns the number of processors installed in the system.  

52.          ///  

53.          /// If the number of processors cannot be determined, returns 1.  

54.            

55.      static Poco::UInt32 libraryVersion();  

56.          /// Returns the POCO C++ Libraries version as a hexadecimal  

57.          /// number in format 0xAABBCCDD, where  

58.          ///    - AA is the major version number,  

59.          ///    - BB is the minor version number,  

60.          ///    - CC is the revision number, and  

61.          ///    - DD is the patch level number.  

62.          ///  

63.          /// Some patch level ranges have special meanings:  

64.          ///    - Dx mark development releases,  

65.          ///    - Ax mark alpha releases, and  

66.          ///    - Bx mark beta releases.  

67.  };  


          从定义中我们可以看到,它的功能包括:

         1.  获取系统第一块网卡的信息

         2.  获取、设置指定名称的环境变量值

         3.  获取操作系统名称、版本、结构

         4.  获取处理器数量

 

         下面是其的一个使用例子:

[cpp] view plaincopy

1.   #include "stdafx.h"  

2.   #include "Poco/Environment.h"  

3.   #include   

4.   using Poco::Environment;  

5.     

6.     

7.   int main(int argc, char** argv)  

8.   {  

9.       std::cout  

10.          << "OS Name: " << Environment::osName() << std::endl  

11.          << "OS Version: " << Environment::osVersion() << std::endl  

12.          << "OS Arch: " << Environment::osArchitecture() << std::endl  

13.          << "Node Name: " << Environment::nodeName() << std::endl  

14.          << "Node ID: " << Environment::nodeId() << std::endl  

15.          << "Processor Count: " << Environment::processorCount() << std::endl  

16.          << "Library Version: " << Environment::libraryVersion() << std::endl;  

17.    

18.      if (Environment::has("TEMP"))  

19.          std::cout << "TEMP: " << Environment::get("TEMP") << std::endl;  

20.      Environment::set("POCO""foo");  

21.    

22.      return 0;  

23.    

24.  }  

 

         Environment的内部的实现上很简单,依赖于EnvironmentImpl类,每中操作系统实现了自己的EnvironmentImpl类,从而实现了对不同操作系统统一接口。

 

POCO C++库学习和分析 -- 日期与时间



        在Poco库中,与时间和日期相关的一些类,其内部实现是非常简单的。看相关文档时,比较有意思的倒是历史上的不同时间表示法。

1. 系统时间函数

        在编程时,时间函数不可避免的会被使用。linux系统下相关时间的数据结构有time_t,timeval,timespec,tm,clock_t; windows下time_t,tm,SYSTEMTIME,FILETIME,clock_t。其中clock_t、timeval、timespec用于表示时间跨度,time_t、tm、SYSTEMTIME,FILETIME用于表示绝对时间。不同的数据结构之间,多少也有些差异。

        首先这些时间结构体的精度不同,Second(time_t/tm), microsecond(timeval/SYSTEMTIME),  100nanoSeconds(FILETIME),nanoSeconds(timespec)。还有一些结构和操作系统相关,如clock_t,windows下为1毫秒,POSIX 下为1微秒,对应clock_t,不同平台的差异,可以用宏CLOCKS_PER_SEC解决。

        起始时间不同,time_t起始于1970年1月1日0时0分0秒,tm表示起始于1900年,SYSTEMTIME/FILETIME起始于1601年,clock起始于机器开机。

        上面各个时间的定义如下:

[cpp] view plaincopy

1.   typedef struct _FILETIME {    

2.                             DWORD dwLowDateTime;    

3.                             DWORD dwHighDateTime;    

4.   } FILETIME, *PFILETIME;    

5.     

6.   typedef struct _SYSTEMTIME  

7.   {  

8.           WORD wYear;  

9.           WORD wMonth;  

10.          WORD wDayOfWeek;  

11.          WORD wDay;  

12.          WORD wHour;  

13.          WORD wMinute;  

14.          WORD wSecond;  

15.          WORD wMilliseconds; // 毫秒  

16.  } SYSTEMTIME, *PSYSTEMTIME;  

17.    

18.  struct tm  

19.  {  

20.          int tm_sec;     /*  – 取值区间为[0,59] */  

21.          int tm_min;     /*  - 取值区间为[0,59] */  

22.          int tm_hour;    /*  - 取值区间为[0,23] */  

23.          int tm_mday;    /* 一个月中的日期 - 取值区间为[1,31] */  

24.          int tm_mon;     /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */  

25.          int tm_year;    /* 年份,其值等于实际年份减去1900 */  

26.          int tm_wday;    /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */  

27.          int tm_yday;    /* 从每年的11日开始的天数 – 取值区间为[0,365],其中0代表11日,1代表12日,以此类推 */  

28.          int tm_isdst;   /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst0;不了解情况时,tm_isdst()为负。*/  

29.  };  

30.    

31.    

32.  typedef __time64_t time_t;  //   

33.    

34.    

35.  struct timeval  

36.  {  

37.          long    tv_sec;         /* seconds */  

38.          long    tv_usec;        /* and microseconds 毫秒*/  

39.  };  

40.    

41.    

42.  struct timespec  

43.  {  

44.          __time_t tv_sec;  /*seconds */  

45.          long int tv_nsec; /*nanoseconds 纳秒*/  

46.  }  

47.    

48.  typedef unsigned long clock_t;  // 毫秒  




        同这些数据结构相关联,C语言为tm,time_t提供了一组函数用于时间运算和数据结构转换:

[cpp] view plaincopy

1.   // 日历时间(一个用time_t表示的整数)  

2.     

3.   // 比较日历时间  

4.   double difftime(time_t time1, time_t time0);  

5.   // 获取日历时间  

6.   time_t time(time_t * timer);  

7.   // 转换日历时间为字符串  

8.   char * ctime(const time_t *timer);  

9.   // 转换日历时间为我们平时看到的把年月日时分秒分开显示的时间格式tm(GMT timezone)  

10.  struct tm * gmtime(const time_t *timer);        

11.  // 转换日历时间为我们平时看到的把年月日时分秒分开显示的时间格式tm(本地 timezone)  

12.  struct tm * localtime(const time_t * timer);  

13.  // 关于本地时间的计算公式:  

14.  localtime = utctime[Gmt time] + utcOffset()[时区偏移] + dst()[夏令时偏移]  

15.    

16.    

17.  // tm转换为字符串  

18.  char * asctime(const struct tm * timeptr);  

19.  // tm转换为日历时间  

20.  time_t mktime(struct tm * timeptr);  

21.    

22.    

23.  // 获取开机以来的微秒数  

24.  clock_t clock (void);  

 

       Windows下特有的时间转换函数包括:

       GetLocalTime能够得到本地电脑设置时区的时间,得到的类型是SYSTEMTIME的类型。

[cpp] view plaincopy

1.   void GetSystemTime(LPSYSTEMTIME lpSystemTime);          // GetSystemTime函数获得当前的UTC时间  

2.   void GetLocalTime(LPSYSTEMTIME lpSystemTime);           // GetLocalTime获得当前的本地时间  

3.     

4.   BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime,    

5.                             LPFILETIME lpFileTime);    

6.   BOOL FileTimeToSystemTime(const FILETIME* lpFileTime,    

7.                             LPSYSTEMTIME lpSystemTime);    

8.   BOOL LocalFileTimeToFileTime(const FILETIME* lpLocalFileTime,    

9.                                LPFILETIME lpFileTime);    

10.  BOOL FileTimeToLocalFileTime(const FILETIME* lpFileTime,    

11.                               LPFILETIME lpLocalFileTime);    

 

      Windows下特有的获取时间精度的函数包括(精度微秒):

[cpp] view plaincopy

1.   BOOL  QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);  

2.   BOOL  QueryPerformanceCounter(LARGE_INTEGER *lpCount);  

 

        我们回想一下程序中的时间数据结构和函数的用法,可以发现主要是2个目的:
        1. 获取绝对时间
        2. 获取两个时间点的相对时间



2. Timestamp类

        同C语言中函数类似,Poco中定义了自己的时间类。Timestamp类似于time_t,用于获取比较日历时间。Timestamp定义如下:

 

[cpp] view plaincopy

1.   class Foundation_API Timestamp  

2.   {  

3.   public:  

4.       typedef Int64 TimeVal;    /// monotonic UTC time value in microsecond resolution  

5.       typedef Int64 UtcTimeVal; /// monotonic UTC time value in 100 nanosecond resolution  

6.       typedef Int64 TimeDiff;   /// difference between two timestamps in microseconds  

7.     

8.       Timestamp();  

9.           /// Creates a timestamp with the current time.  

10.            

11.      Timestamp(TimeVal tv);  

12.          /// Creates a timestamp from the given time value.  

13.            

14.      Timestamp(const Timestamp& other);  

15.          /// Copy constructor.  

16.            

17.      ~Timestamp();  

18.          /// Destroys the timestamp  

19.            

20.      Timestamp& operator = (const Timestamp& other);  

21.      Timestamp& operator = (TimeVal tv);  

22.        

23.      void swap(Timestamp& timestamp);  

24.          /// Swaps the Timestamp with another one.  

25.        

26.      void update();  

27.          /// Updates the Timestamp with the current time.  

28.    

29.    

30.      bool operator == (const Timestamp& ts) const;  

31.      bool operator != (const Timestamp& ts) const;  

32.      bool operator >  (const Timestamp& ts) const;  

33.      bool operator >= (const Timestamp& ts) const;  

34.      bool operator <  (const Timestamp& ts) const;  

35.      bool operator <= (const Timestamp& ts) const;  

36.        

37.      Timestamp  operator +  (TimeDiff d) const;  

38.      Timestamp  operator -  (TimeDiff d) const;  

39.      TimeDiff   operator -  (const Timestamp& ts) const;  

40.      Timestamp& operator += (TimeDiff d);  

41.      Timestamp& operator -= (TimeDiff d);  

42.        

43.      std::time_t epochTime() const;  

44.          /// Returns the timestamp expressed in time_t.  

45.          /// time_t base time is midnight, January 1, 1970.  

46.          /// Resolution is one second.  

47.            

48.      UtcTimeVal utcTime() const;  

49.          /// Returns the timestamp expressed in UTC-based  

50.          /// time. UTC base time is midnight, October 15, 1582.  

51.          /// Resolution is 100 nanoseconds.  

52.        

53.      TimeVal epochMicroseconds() const;  

54.          /// Returns the timestamp expressed in microseconds  

55.          /// since the Unix epoch, midnight, January 1, 1970.  

56.        

57.      TimeDiff elapsed() const;  

58.          /// Returns the time elapsed since the time denoted by  

59.          /// the timestamp. Equivalent to Timestamp() - *this.  

60.        

61.      bool isElapsed(TimeDiff interval) const;  

62.          /// Returns true iff the given interval has passed  

63.          /// since the time denoted by the timestamp.  

64.        

65.      static Timestamp fromEpochTime(std::time_t t);  

66.          /// Creates a timestamp from a std::time_t.  

67.            

68.      static Timestamp fromUtcTime(UtcTimeVal val);  

69.          /// Creates a timestamp from a UTC time value.  

70.            

71.      static TimeVal resolution();  

72.          /// Returns the resolution in units per second.  

73.          /// Since the timestamp has microsecond resolution,  

74.          /// the returned value is always 1000000.  

75.    

76.  private:  

77.      TimeVal _ts;  

78.  };  


        Timestamp内部定义了一个Int64的变量_ts。存储了一个基于utc时间的64位int值,理论上可以提供微秒级的精度(实际精度依赖于操作系统)。由于Poco::Timestamp是基于UTC(世界标准时间或世界協調時間)的,所以它是独立于时区设置的。Poco::Timestamp实现了值语义,比较和简单的算术操作。
        1. UTC(Coordinated Universal Time)是从1582年10月15日深夜开始计时的. Poco库中精度为100纳秒。
        2. epochtime指是从1970年1月1日深夜开始计时的(指unix诞生元年)。Poco库中精度为1秒。

       数据类型:
        Poco::Timestamp内部定义了下列数据类型:
        1. TimeVal
          一个64位的int整数值,保存utc时间,精度微秒
        2. UtcTimeVal
         一个64位的int整数值,保存utc时间,精度100纳秒(真实精度仍然是微秒)
        3. TimeDiff
        一个64位的int整数值,保存两个Timestamp的差值,精度微秒


       构造函数:
        1. 默认构造函数会以当前时间初始化一个Timestamp值,基于UTC时间(从1582年10月15日开始计时,精度为100纳秒)
        2. 提供了两个静态函数用于创建Timestamp对象,
                  a) Timestamp fromEpochTime(time_ttime)。这个函数从time_t构建,内部会把EpochTime(从1970年1月1日深夜开始计时的,精度为1秒)的时间转换成为UTC时间。
                  b) Timestamp fromUtcTime(UtcTimeVal val)。这个函数从一个UtcTimeVal构建。


        Timestamp的成员函数:
        1. time_t epochTime() const
        返回一个以epoch time计算的日历时间(精度秒)。(函数内部会把基于UTC时间的值转为基于epoch time的值)
        2. UtcTimeVal utcTime() const
        返回一个以UTC时间计算的日历时间(精度100纳秒)。
        3. TimeVal epochMicroseconds() const
        返回一个以epoch time计算的日历时间(精度微秒)
        4. void update()
        取当前的时间更新
        5. TimeDiff elapsed() const
        返回当前时间与Timestamp内部时间_ts的一个时间差值(精度微秒)
        6. bool isElapsed(TimeDiff interval) const
        如果当前时间与Timestamp内部时间_ts的一个时间差值大于interval时间,返回true。(精度微秒)

       Timestamp算术计算:
        1. Timestamp operator + (TimeDiff diff) const
        增加一个时间偏移,并返回值。(精度微秒)
        2. Timestamp operator - (TimeDiff diff) const
        减掉一个时间偏移,并返回值。(精度微秒)
        3. TimeDiff operator - (const Timestamp&ts) const
        返回两个Timestamp对象的时间偏移。(精度微秒)
        4. Timestamp& operator += (TimeDiff d)
            Timestamp& operator -=(TimeDiff d)
        增加或减小一个时间偏移值

        下面来看一个例子:

[cpp] view plaincopy

1.   #include "Poco/Timestamp.h"  

2.   #include   

3.   using Poco::Timestamp;  

4.   int main(int argc, char** argv)  

5.   {  

6.       Timestamp now; // the current date and time  

7.       std::time_t t1 = now.epochTime(); // convert to time_t ...  

8.       Timestamp ts1(Timestamp::fromEpochTime(t1)); // ... and back again  

9.       for (int i = 0; i < 100000; ++i) ; // wait a bit  

10.      Timestamp::TimeDiff diff = now.elapsed(); // how long did it take?  

11.      Timestamp start(now); // save start time  

12.      now.update(); // update with current  

13.      time diff = now - start; // again, how long?  

14.      return 0;  

15.  }  




3. DateTime类

        Poco中提供了DateTime类,作用和tm类似。下面是它的定义:

[cpp] view plaincopy

1.   class Foundation_API DateTime  

2.   {  

3.   public:  

4.       enum Months  

5.           /// Symbolic names for month numbers (1 to 12).  

6.       {  

7.           JANUARY = 1,  

8.           FEBRUARY,  

9.           MARCH,  

10.          APRIL,  

11.          MAY,  

12.          JUNE,  

13.          JULY,  

14.          AUGUST,  

15.          SEPTEMBER,  

16.          OCTOBER,  

17.          NOVEMBER,  

18.          DECEMBER  

19.      };  

20.        

21.      enum DaysOfWeek  

22.          /// Symbolic names for week day numbers (0 to 6).  

23.      {  

24.          SUNDAY = 0,  

25.          MONDAY,  

26.          TUESDAY,  

27.          WEDNESDAY,  

28.          THURSDAY,  

29.          FRIDAY,  

30.          SATURDAY  

31.      };  

32.            

33.      DateTime();  

34.          /// Creates a DateTime for the current date and time.  

35.    

36.    

37.      DateTime(const Timestamp& timestamp);  

38.          /// Creates a DateTime for the date and time given in  

39.          /// a Timestamp.  

40.            

41.      DateTime(int year, int month, int day, int hour = 0, int minute = 0, int   

42.    

43.    

44.  second = 0, int millisecond = 0, int microsecond = 0);  

45.          /// Creates a DateTime for the given Gregorian date and time.  

46.          ///   * year is from 0 to 9999.  

47.          ///   * month is from 1 to 12.  

48.          ///   * day is from 1 to 31.  

49.          ///   * hour is from 0 to 23.  

50.          ///   * minute is from 0 to 59.  

51.          ///   * second is from 0 to 59.  

52.          ///   * millisecond is from 0 to 999.  

53.          ///   * microsecond is from 0 to 999.  

54.    

55.    

56.      DateTime(double julianDay);  

57.          /// Creates a DateTime for the given Julian day.  

58.    

59.    

60.      DateTime(Timestamp::UtcTimeVal utcTime, Timestamp::TimeDiff diff);  

61.          /// Creates a DateTime from an UtcTimeVal and a TimeDiff.  

62.          ///  

63.          /// Mainly used internally by DateTime and friends.  

64.    

65.    

66.      DateTime(const DateTime& dateTime);  

67.          /// Copy constructor. Creates the DateTime from another one.  

68.    

69.    

70.      ~DateTime();  

71.          /// Destroys the DateTime.  

72.    

73.    

74.      DateTime& operator = (const DateTime& dateTime);  

75.          /// Assigns another DateTime.  

76.            

77.      DateTime& operator = (const Timestamp& timestamp);  

78.          /// Assigns a Timestamp.  

79.    

80.    

81.      DateTime& operator = (double julianDay);  

82.          /// Assigns a Julian day.  

83.    

84.    

85.      DateTime& assign(int year, int month, int day, int hour = 0, int minute = 0,   

86.    

87.    

88.  int second = 0, int millisecond = 0, int microseconds = 0);  

89.          /// Assigns a Gregorian date and time.  

90.          ///   * year is from 0 to 9999.  

91.          ///   * month is from 1 to 12.  

92.          ///   * day is from 1 to 31.  

93.          ///   * hour is from 0 to 23.  

94.          ///   * minute is from 0 to 59.  

95.          ///   * second is from 0 to 59.  

96.          ///   * millisecond is from 0 to 999.  

97.          ///   * microsecond is from 0 to 999.  

98.    

99.    

100.     void swap(DateTime& dateTime);  

101.         /// Swaps the DateTime with another one.  

102.   

103.   

104.     int year() const;  

105.         /// Returns the year.  

106.           

107.     int month() const;  

108.         /// Returns the month (1 to 12).  

109.       

110.     int week(int firstDayOfWeek = MONDAY) const;  

111.         /// Returns the week number within the year.  

112.         /// FirstDayOfWeek should be either SUNDAY (0) or MONDAY (1).  

113.         /// The returned week number will be from 0 to 53. Week number 1 is   

114.   

115.   

116. the week   

117.         /// containing January 4. This is in accordance to ISO 8601.  

118.         ///   

119.         /// The following example assumes that firstDayOfWeek is MONDAY. For 2005, which started  

120.         /// on a Saturday, week 1 will be the week starting on Monday, January 3.  

121.         /// January 1 and 2 will fall within week 0 (or the last week of the previous year).  

122.         ///  

123.         /// For 2007, which starts on a Monday, week 1 will be the week   

124.   

125.   

126. startung on Monday, January 1.  

127.         /// There will be no week 0 in 2007.  

128.       

129.     int day() const;  

130.         /// Returns the day witin the month (1 to 31).  

131.           

132.     int dayOfWeek() const;  

133.         /// Returns the weekday (0 to 6, where  

134.         /// 0 = Sunday, 1 = Monday, ..., 6 = Saturday).  

135.       

136.     int dayOfYear() const;  

137.         /// Returns the number of the day in the year.  

138.         /// January 1 is 1, February 1 is 32, etc.  

139.       

140.     int hour() const;  

141.         /// Returns the hour (0 to 23).  

142.           

143.     int hourAMPM() const;  

144.         /// Returns the hour (0 to 12).  

145.       

146.     bool isAM() const;  

147.         /// Returns true if hour < 12;  

148.   

149.   

150.     bool isPM() const;  

151.         /// Returns true if hour >= 12.  

152.           

153.     int minute() const;  

154.         /// Returns the minute (0 to 59).  

155.           

156.     int second() const;  

157.         /// Returns the second (0 to 59).  

158.           

159.     int millisecond() const;  

160.         /// Returns the millisecond (0 to 999)  

161.       

162.     int microsecond() const;  

163.         /// Returns the microsecond (0 to 999)  

164.       

165.     double julianDay() const;  

166.         /// Returns the julian day for the date and time.  

167.           

168.     Timestamp timestamp() const;  

169.         /// Returns the date and time expressed as a Timestamp.  

170.   

171.   

172.     Timestamp::UtcTimeVal utcTime() const;  

173.         /// Returns the date and time expressed in UTC-based  

174.         /// time. UTC base time is midnight, October 15, 1582.  

175.         /// Resolution is 100 nanoseconds.  

176.           

177.     bool operator == (const DateTime& dateTime) const;    

178.     bool operator != (const DateTime& dateTime) const;    

179.     bool operator <  (const DateTime& dateTime) const;     

180.     bool operator <= (const DateTime& dateTime) const;     

181.     bool operator >  (const DateTime& dateTime) const;     

182.     bool operator >= (const DateTime& dateTime) const;     

183.   

184.   

185.     DateTime  operator +  (const Timespan& span) const;  

186.     DateTime  operator -  (const Timespan& span) const;  

187.     Timespan  operator -  (const DateTime& dateTime) const;  

188.     DateTime& operator += (const Timespan& span);  

189.     DateTime& operator -= (const Timespan& span);  

190.       

191.     void makeUTC(int tzd);  

192.         /// Converts a local time into UTC, by applying the given time zone   

193.   

194.   

195. differential.  

196.           

197.     void makeLocal(int tzd);  

198.         /// Converts a UTC time into a local time, by applying the given time   

199.   

200.   

201. zone differential.  

202.       

203.     static bool isLeapYear(int year);  

204.         /// Returns true if the given year is a leap year;  

205.         /// false otherwise.  

206.           

207.     static int daysOfMonth(int year, int month);  

208.         /// Returns the number of days in the given month  

209.         /// and year. Month is from 1 to 12.  

210.           

211.     static bool isValid(int year, int month, int day, int hour = 0, int minute =   

212.   

213.   

214. 0, int second = 0, int millisecond = 0, int microsecond = 0);  

215.         /// Checks if the given date and time is valid  

216.         /// (all arguments are within a proper range).  

217.         ///  

218.         /// Returns true if all arguments are valid, false otherwise.  

219.           

220. protected:    

221.     // ...  

222.   

223. private:  

224.     // ...  

225.   

226.     Timestamp::UtcTimeVal _utcTime;  

227.     short  _year;  

228.     short  _month;  

229.     short  _day;  

230.     short  _hour;  

231.     short  _minute;  

232.     short  _second;  

233.     short  _millisecond;  

234.     short  _microsecond;  

235. };  


        Poco::DateTime是基于格里高利历(Gregorian calendar)(就是公历啦)设计的。它除了可以用来保存日历时间外,还可以被用于日期计算。如果只是为了日历时间的存储,Timestamp类更加适合。在DateTime的内部,DateTime类用两种格式维护了日期和时间。第一种是UTC日历时间。第二种是用年、月、日、时、分、秒、微秒、毫秒。为了进行内部时间日期之间的换算,DateTime使用了儒略日(Julianday)历法。

       格里高利历(Gregoriancalendar)
        格里高利历就是我们通常讲的公历。它以耶稣的诞生为初始年份,也就是公元0001年。格里高利历以日为基本单位,1年有365或366天,分成12个月,每个月时长不等。由于不同国家采用格里高利历时间不同(德国1582,英国1752),所以格里高利历日期和旧式的日期有差别,即使是用来表示历史上相同的一件事。一个耶稣像,^_^。
      _      xxxx     _
     /_;-.__ / _\  _.-;_\
        `-._`'`_/'`.-'
            `\  /`
            |  /
            /-.(
            \_._\
            \ \`;
             > |/
            / //
            |//
            \(\
        

       儒略日和儒略日日期
       
儒略日的起点订在公元前4713年(天文学上记为 -4712年)1月1日格林威治时间平午(世界时12:00),即JD 0指定为UT时间B.C.4713年1月1日12:00到UC时间B.C.4713年1月2日12:00的24小时。注意这一天是礼拜一。每一天赋予了一个唯一的数字,顺数而下,如:1996年1月1日12:00:00的儒略日是2450084。这个日期是考虑了太阳、月亮的轨道运行周期,以及当时收税的间隔而订出来的。Joseph Scliger定义儒略周期为7980年,是因28、19、15的最小公倍数为28×19×15=7980。

       日期的注意事项:
       
1.0是一个合法数字(根据ISO 8691和天文年编号)
       2. 0年是一个闰年。
       3. 负数是不支持的。比如说公元前1年。
       4. 格里高利历同历史上的日期可能不同,由于它们采用的日历方式不同。
       5. 最好只是用DateTime用来计算当前的时间。对于历史上的或者天文日历的时间计算,还是使用其他的特定软件。

       构造DateTime:
       
1.可以从一个已有的DateTime构造
       2. 当前的时间和日期
       3. 一个Timestamp对象
       4. 用年、月、日、时、分、秒、微秒、毫秒构造
       5. 使用一个儒略日日期构造(用double形式保存)


       成员函数:
       
1.int year() const
         返回年
       2. intmonth() const
          返回月(1-12)
       3. intweek(int firstDayOfWeek = DateTime::MONDAY) const
          返回所在的周,根据ISO 8601标准(第一周是1月4日所在的周),一周的第一天是礼拜一或者礼拜天。
       4. intday() const
          返回月中的所在天(1 - 31)
       5. intdayOfWeek() const
           返回周中的所在天 0为周日,1为周一,.....
       6. intdayOfYear() const
           返回年中的所在天(1 - 366)
       7. inthour() const
           返回天中所在小时(0 - 23)
       8. inthourAMPM() const
           返回上下午所在小时(0 - 12)
       9.bool isAM() const
           如果上午返回真
       10.bool isPM() const
           如果下午返回真
       11.int minute() const
           返回分钟数(0 - 59)
       12.int second() const
          返回秒数(0 - 59)
       13.int millisecond() const
           返回毫秒数(0 - 999)
       14.int microsecond() const
           返回微秒数(0 - 999)
       15.Timestamp timestamp() const
           返回用Timestamp保存的日历时间(精度微秒)
       16.Timestamp::UtcTimeVal utcTime() const
           返回用Timestamp保存的日历时间(精度100纳秒)
       17.DateTime支持关系运算符(==, !=, >, >=, <, <=).
       18.DateTime支持算术操作(+, -, +=, -=)

       静态函数:
       
1.bool isLeapYear(int year)
           所给年是否闰年
       2. intdaysOfMonth(int year, int month)
           所给年和月的天数
       3.bool isValid(int year, int month, int day, int hour, int minute, int second,int millisecond, int microsecond)
          判断所给年月日是否合法

       下面是DateTime的一个例子:

 

[cpp] view plaincopy

1.   #include "Poco/DateTime.h"  

2.   using Poco::DateTime;  

3.   int main(int argc, char** argv)  

4.   {  

5.       DateTime now; // the current date and time in UTC  

6.       int year = now.year();  

7.       int month = now.month();  

8.       int day = now.day();  

9.       int dow = now.dayOfWeek();  

10.      int doy = now.dayOfYear();  

11.      int hour = now.hour();  

12.      int hour12 = now.hourAMPM();  

13.      int min = now.minute();  

14.      int sec = now.second();  

15.      int ms = now.millisecond();  

16.      int us = now.microsecond();  

17.      double jd = now.julianDay();  

18.      Poco::Timestamp ts = now.timestamp();  

19.      DateTime xmas(2006, 12, 25); // 2006-12-25 00:00:00  

20.      Poco::Timespan timeToXmas = xmas - now;  

21.    

22.    

23.      DateTime dt(1973, 9, 12, 2, 30, 45); // 1973-09-12 02:30:45  

24.      dt.assign(2006, 10, 13, 13, 45, 12, 345); // 2006-10-13 12:45:12.345  

25.      bool isAM = dt.isAM(); // false  

26.      bool isPM = dt.isPM(); // true  

27.      bool isLeap = DateTime::isLeapYear(2006); // false  

28.      int days = DateTime::daysOfMonth(2006, 2); // 28  

29.      bool isValid = DateTime::isValid(2006, 02, 29); // false  

30.      dt.assign(2006, DateTime::OCTOBER, 22); // 2006-10-22 00:00:00  

31.      if (dt.dayOfWeek() == DateTime::SUNDAY)  

32.      {  

33.          // ...  

34.      }  

35.      return 0;  

36.  }  

 



4. LocalDateTime类

        Poco::LocalDateTime同Poco::DateTime类似,不同的是Poco::LocalDateTime存储一个本地时间。关于本地时间和UTC时间有如下计算公式:
               (UTC时间 = 本地时间 - 时区差).

       构造函数:
        
1. 通过当前时间构造
        2. 通过Timestamp对象
        3. 通过年、月、日、时、分、秒、微秒、毫秒构造
        4. 通过儒略日时间构造(儒略日时间用double存储)
        5. 作为可选项。时区可作为构造时第一个参数被指定。(如果没有指定的话,会使用系统当前的时区)

       成员函数:
        
1. LocalDateTime支持所有的DateTime的函数。
        2. 在进行比较之前,所有的关系操作符函数会把时间都换算为UTC时间
        3. int tzd() const
           返回时区差
        4. DateTime utc() const
           转换本地时间到utc时间

        下面是一个例子:

[cpp] view plaincopy

1.   #include "Poco/LocalDateTime.h"  

2.   using Poco::LocalDateTime;  

3.   int main(int argc, char** argv)  

4.   {  

5.       LocalDateTime now; // the current date and local time  

6.       int year = now.year();  

7.       int month = now.month();  

8.       int day = now.day();  

9.       int dow = now.dayOfWeek();  

10.      int doy = now.dayOfYear();  

11.      int hour = now.hour();  

12.      int hour12 = now.hourAMPM();  

13.      int min = now.minute();  

14.      int sec = now.second();  

15.      int ms = now.millisecond();  

16.      int us = now.microsecond();  

17.      int tzd = now.tzd();  

18.      double jd = now.julianDay();  

19.      Poco::Timestamp ts = now.timestamp();  

20.    

21.    

22.      LocalDateTime dt1(1973, 9, 12, 2, 30, 45); // 1973-09-12 02:30:45  

23.      dt1.assign(2006, 10, 13, 13, 45, 12, 345); // 2006-10-13 12:45:12.345  

24.      LocalDateTime dt2(3600, 1973, 9, 12, 2, 30, 45, 0, 0); // UTC +1 hour  

25.      dt2.assign(3600, 2006, 10, 13, 13, 45, 12, 345, 0);  

26.      Poco::Timestamp nowTS;  

27.      LocalDateTime dt3(3600, nowTS); // construct from Timestamp  

28.      return 0;  

29.  }  




5. Timespan类

        Poco::Timespan能够提供一个微秒精度的时间间隔,也可以用天、小时、分钟、秒、微秒、毫秒来表示。在其内部这个时间间隔用一个64-bit整形来表示。

       构造函数:
        
1. 一个TimeStamp::TimeDiff对象(微秒精度)
        2. 秒+微秒
           主要用于从timeval结构体构建
        3. 通过日、时、分、秒、微秒构造

       操作符:
        
1. Poco::Timespan支持所有的关系操作符
        (==, !=, <, <=, >, >=)
        2. Poco::Timespan支持加法和减法操作
        (+, -, +=, -=)

       成员函数:
        
1. int days() const
           返回时间跨度的天
        2. int hours() const
           返回时间跨度的小时(0 - 23)
        3. int totalHours() const
           返回时间跨度总的小时数
        4. int minutes() const
           返回时间跨度的分钟(0 - 59)
        5. int totalMinutes() const
           返回时间跨度总的分钟数
        6. int seconds() const
           返回时间跨度的秒(0 - 60)
        7. int totalSeconds() const
           返回时间跨度总的秒数
        8. int milliseconds() const
           返回时间跨度的毫秒((0 - 999)
        9. int totalMilliseconds() const
           返回时间跨度总的毫秒数
        10. int microseconds() const
           返回时间跨度的微秒( (0 - 999)
        11. int totalMicroseconds() const
           返回时间跨度总的微秒数

        下面是一个例子:

[cpp] view plaincopy

1.   #include "Poco/Timespan.h"  

2.   using Poco::Timespan;  

3.   int main(int argc, char** argv)  

4.   {  

5.       Timespan ts1(1, 11, 45, 22, 123433); // 1d 11h 45m 22.123433s  

6.       Timespan ts2(33*Timespan::SECONDS); // 33s  

7.       Timespan ts3(2*Timespan::DAYS + 33*Timespan::HOURS); // 3d 33h  

8.       int days = ts1.days(); // 1  

9.       int hours = ts1.hours(); // 11  

10.      int totalHours = ts1.totalHours(); // 35  

11.      int minutes = ts1.minutes(); // 45  

12.      int totalMins = ts1.totalMinutes(); // 2145  

13.      int seconds = ts1.seconds(); // 22  

14.      int totalSecs = ts1.totalSeconds(); // 128722  

15.      return 0;  

16.  }  


        下面来看一个DateTime,LocalDateTime和Timespan的混合例子:

[cpp] view plaincopy

1.   #include "Poco/DateTime.h"  

2.   #include "Poco/Timespan.h"  

3.   using Poco::DateTime;  

4.   using Poco::Timespan;  

5.   int main(int argc, char** argv)  

6.   {  

7.       // what is my age?  

8.       DateTime birthdate(1973, 9, 12, 2, 30); // 1973-09-12 02:30:00  

9.       DateTime now;  

10.      Timespan age = now - birthdate;  

11.      int days = age.days(); // in days  

12.      int hours = age.totalHours(); // in hours  

13.      int secs = age.totalSeconds(); // in seconds  

14.      // when was I 10000 days old?  

15.      Timespan span(10000*Timespan::DAYS);  

16.      DateTime dt = birthdate + span;  

17.      return 0;  

18.  }  




6. Timezone类

        Poco::Timezone提供静态函数用于获取时区信息和夏令时信息。
        1. 时区差
        2. 是否采用夏时制(daylight saving time (DST))
        3. 时区名

       成员函数:
        
1. int utcOffset()
           返回本地时间相对于UTC时间的差值(精度秒)。不包括夏令时偏移:
                  (localtime = UTC + utcOffset())
        2. int dst()
          返回夏令时偏移。通常是固定值3600秒。0的话表示无夏令时。
        3. bool isDst(const Timestamp& timestamp)
          对于给定的timestamp时间测试,是否使用夏令时
        4. int tzd()
          返回本地时间相对于UTC时间的差值(精度秒)。包括夏令时偏移:
           (tzd = utcOffset() + dst())
        5. std::string name()
           返回当前的时区名
        6. std::string standardName()
           返回当前的标准时区名(如果不采用夏令时)
        7. std::string dstName()
           返回当前的时区名(如果采用夏令时)
        8. 时区名的返回依赖于操作系统,并且名称不具有移植性,仅作显示用。

        下面是一个使用的例子:

[cpp] view plaincopy

1.   #include "Poco/Timezone.h"  

2.   #include "Poco/Timestamp.h"  

3.   using Poco::Timezone;  

4.   using Poco::Timestamp;  

5.   int main(int argc, char** argv)  

6.   {  

7.       int utcOffset = Timezone::utcOffset();  

8.       int dst = Timezone::dst();  

9.       bool isDst = Timezone::isDst(Timestamp());  

10.      int tzd = Timezone::tzd();  

11.      std::string name = Timezone::name();  

12.      std::string stdName = Timezone::standardName();  

13.      std::string dstName = Timezone::dstName();  

14.      return 0;  

15.  }  




6. Poco::DateTimeFormatter类

        Poco::DateTimeFormatter用来定义当Timestamp,DateTime, LocalDateTime and Timespan转换为字符串时所需的日期和事件格式。Poco::DateTimeFormatter的作用和strftime()是类似的。Poco::DateTimeFormat内部定义了一些约定的格式。
        1. ISO8601_FORMAT (2005-01-01T12:00:00+01:00)
        2. RFC1123_FORMAT (Sat, 1 Jan 2005 12:00:00 +0100)
        3. SORTABLE_FORMAT (2005-01-01 12:00:00)
        4. For more information, please see the referencedocumentation.

       成员函数:
        
所有的DateTimeFormatter函数都是静态的。

        下面是一个使用的例子:

[cpp] view plaincopy

1.   #include "Poco/Timestamp.h"  

2.   #include "Poco/Timespan.h"  

3.   #include "Poco/DateTimeFormatter.h"  

4.   #include "Poco/DateTimeFormat.h"  

5.   using Poco::DateTimeFormatter;  

6.   using Poco::DateTimeFormat;  

7.   int main(int argc, char** argv)  

8.   {  

9.       Poco::DateTime dt(2006, 10, 22, 15, 22, 34);  

10.      std::string s(DateTimeFormatter::format(dt, "%e %b %Y %H:%M"));  

11.      // "22 Oct 2006 15:22"  

12.      Poco::Timestamp now;  

13.      s = DateTimeFormatter::format(now, DateTimeFormat::SORTABLE_FORMAT);  

14.      // "2006-10-30 09:27:44"  

15.      Poco::Timespan span(5, 11, 33, 0, 0);  

16.      s = DateTimeFormatter::format(span, "%d days, %H hours, %M minutes");  

17.      // "5 days, 11 hours, 33 minutes"  

18.      return 0;  

19.  }  




7. Poco::DateTimeParser类

        Poco::DateTimeParser用来从字符串中解析时间和日期。下面是其一个例子:

[cpp] view plaincopy

1.   #include "Poco/DateTimeParser.h"  

2.   #include "Poco/DateTime.h"  

3.   #include "Poco/DateTimeFormat.h"  

4.   #include "Poco/LocalDateTime.h"  

5.   #include "Poco/Timestamp.h"  

6.   using Poco::DateTimeParser;  

7.   using Poco::DateTimeFormat;  

8.   using Poco::DateTime;  

9.   int main(int argc, char** argv)  

10.  {  

11.      std::string s("Sat, 1 Jan 2005 12:00:00 GMT");  

12.      int tzd;  

13.      DateTime dt;  

14.      DateTimeParser::parse(DateTimeFormat::RFC1123_FORMAT, s, dt, tzd);  

15.      Poco::Timestamp ts = dt.timestamp();  

16.      Poco::LocalDateTime ldt(tzd, dt);  

17.      bool ok = DateTimeParser::tryParse("2006-10-22", dt, tzd);  

18.      ok = DateTimeParser::tryParse("%e.%n.%Y""22.10.2006", dt, tzd);  

19.      return 0;  

20.  }  




8. Stopwatch类

        Stopwatch用来测量时间差值,精度为微秒.下面是其定义:

[cpp] view plaincopy

1.   class Foundation_API Stopwatch  

2.       /// A simple facility to measure time intervals  

3.       /// with microsecond resolution.  

4.       ///  

5.       /// Note that Stopwatch is based on the Timestamp  

6.       /// class. Therefore, if during a Stopwatch run,  

7.       /// the system time is changed, the measured time  

8.       /// will not be correct.  

9.   {  

10.  public:  

11.      Stopwatch();  

12.      ~Stopwatch();  

13.    

14.    

15.      void start();  

16.          /// Starts (or restarts) the stopwatch.  

17.            

18.      void stop();  

19.          /// Stops or pauses the stopwatch.  

20.        

21.      void reset();  

22.          /// Resets the stopwatch.  

23.            

24.      void restart();  

25.          /// Resets and starts the stopwatch.  

26.            

27.      Timestamp::TimeDiff elapsed() const;  

28.          /// Returns the elapsed time in microseconds  

29.          /// since the stopwatch started.  

30.            

31.      int elapsedSeconds() const;  

32.          /// Returns the number of seconds elapsed  

33.          /// since the stopwatch started.  

34.    

35.    

36.      static Timestamp::TimeVal resolution();  

37.          /// Returns the resolution of the stopwatch.  

38.    

39.    

40.  private:  

41.      Stopwatch(const Stopwatch&);  

42.      Stopwatch& operator = (const Stopwatch&);  

43.    

44.    

45.      Timestamp           _start;  

46.      Timestamp::TimeDiff _elapsed;  

47.      bool                _running;  

48.  };  

POCO C++库学习和分析 -- 异常、错误处理、调试

 

1. 异常处理

       C++同C语言相比,提供了异常机制。通过使用try,catch关键字可以捕获异常,这种机制使得程序员在程序异常发生时,可以通过判断异常类型,来决定程序是否继续执行,并在程序结束之前优雅的释放各类资源。当然对于C++的异常机制也存在着很多的争议。在这里,并不对此展开讨论,只介绍一下Poco中的异常类。


        Poco中的异常类:
        1. 所有的异常类都是Poco::Exception的子类。
        2. Poco::Exception继承自std::exception类。
        3. Foundation库中涉及的异常类,包括了下面一些:
                  a) Poco::LogicException类负责处理程序错误,包括了:
                          AssertionViolationException
                         NullPointerException
                          NullValueException
                          BugcheckException
                          InvalidArgumentException
                          NotImplementedException
                          RangeException
                          IllegalStateException
                          InvalidAccessException
                          SignalException
                          UnhandledException
                  b) Poco::ApplicationException类负责处理应用程序相关的错误,即使用Poco库的用户自定义异常。
                  c) Poco::RuntimeException类负责处理程序运行时的错误,包括了:
                          RuntimeException
                          NotFoundException
                          ExistsException
                          TimeoutException
                          SystemException
                          RegularExpressionException
                          LibraryLoadException
                          LibraryAlreadyLoadedException
                          NoThreadAvailableException
                          PropertyNotSupportedException
                          PoolOverflowException
                          NoPermissionException
                          OutOfMemoryException
                          DataException
                          DataFormatException
                          SyntaxException
                          CircularReferenceException
                          PathSyntaxException
                          IOException
                          ProtocolException
                          FileException
                          FileExistsException
                          FileNotFoundException
                          PathNotFoundException
                          FileReadOnlyException
                          FileAccessDeniedException
                          CreateFileException
                          OpenFileException
                          WriteFileException
                          ReadFileException
                          UnknownURISchemeException


        成员函数及数据定义:
        1. Poco::Exception包括了一个名字,这是一个静态的字符串,用来描述异常本身。比如说LogicException名字为"Logic exception",TimeoutException名字为"Timeout"。
        2. Poco::Exception还包含了一个字符串消息,这是用来进一步描述异常的。使用的的人可以在运行时定义它。比如都是LogicException异常,函数一处抛出异常时可定义为"Function1",函数二处抛出时异常时可定义为用"Function2",它可以用来说明异常发生的具体位置和原因。
        3. 一个可选的嵌套异常类
        4. 构造函数:
                  a) 可以使用0个,1个或2个字符串参数来构造异常。在Poco::Exception内部存储的时候,第二个字符串会使用字符":"和第一个字符串串联。
                  b) 构造时如果使用了字符串和嵌套异常的方式,嵌套异常会被复制一份。
        5. Poco::Exception支持拷贝和赋值运算符
        6. const char* name()
                   返回异常的名称
        7. const std::string& message()
                   返回在构造时传入的消息字符串
        8. std::string displayText() const
                   同时返回异常名字和消息字符串,中间使用": "分隔
        9. const Exception* nested() const
                   如果存在嵌套异常的话,返回之歌指向嵌套异常的指针,否则返回0
        10. Exception* clone() const
                   返回一个异常的拷贝
        11. void rethrow() const
                   重新抛出异常


        定义自己的异常:
        因为从Poco::Exception继承,去定义自己的异常时,工作非常的枯燥且重复(用户需要重载大量的虚函数),在库中提供了两个宏来完成这个工作:
                  POCO_DECLARE_EXCEPTION:用来申明异常宏
                  POCO_IMPLEMENT_EXCEPTION:用来定义异常宏的执行体


        两个宏分别定义如下:

// MyException.h
#include "Poco/Exception.h"
POCO_DECLARE_EXCEPTION(MyLib_API, MyException, Poco::Exception)
// MyException.cpp
#include "MyException.h"POCO_IMPLEMENT_EXCEPTION(MyException, Poco::Exception,"Something really bad happened...")

 

       宏展开分别为:

// MyException.h
#include "Poco/Exception.h"
POCO_DECLARE_EXCEPTION(MyLib_API, MyException, Poco::Exception)
class MyLib_API MyException: public Poco::Exception
{
public:
            MyException();
            MyException(const std::string& msg);
            MyException(const std::string& msg, const std::string& arg);
            MyException(const std::string& msg, const Poco::Exception& nested);
            MyException(const MyException& exc);
            ~MyException();
            MyException& operator = (const MyException& exc);
            const char* name() const;
            ...
};



// MyException.cpp
#include "MyException.h"
POCO_IMPLEMENT_EXCEPTION(MyException, Poco::Exception,
"Something really bad happened...")
...
const char* MyException::name() const throw()
{
            return "Something really bad happened...";
}
...



        下面是一个例子:

#include "Poco/Exception.h"
#include 
int main(int argc, char** argv)
{
            Poco::Exception* pExc = 0;
            try
            {
                        throw Poco::ApplicationException("just testing");
            }
            catch (Poco::Exception& exc)
            {
                        pExc = exc.clone();
            }
            try
            {
                        pExc->rethrow();
            }
            catch (Poco::Exception& exc)
            {
                        std::cerr << exc.displayText() << std::endl;
            }
            delete pExc;
            return 0;
}



2. 断言

       POCO库中提供了一些断言的宏来进行运行时检查,这些断言能够提供出错代码的行号和文件信息。
        1. Debugger::_assert(cond)
         如果cond ≠ true时,抛出一个AssertionViolationException异常。
        2. poco_assert_dbg(cond)
           同poco_assert类似,但是只在debug模式下起作用
        3. poco_check_ptr(ptr)
           如果ptr为空,则抛出NullPointerException异常
        4. poco_bugcheck(), poco_bugcheck_msg(string)
           抛出BugcheckException异常

        POCO的断言类在debug调试模式下(比如在VisualC++)中时,会触发一个breakpoint。比如:

void foo(Bar* pBar)
{
            poco_check_ptr (pBar);
            ...
}
void baz(int i)
{
            poco_assert (i >= 1 && i < 3);
            switch (i)
            {
            case 1:
                        ...
                                     break;
            case 2:
                        ...
                                     break;
            default:
                        poco_bugcheck_msg("i has invalid value");
            }
}


        这主要是因为Poco中的断言类是通过Poco::Debugger去实现的,在Poco::Debugger底层调用了不同操作系统的API,去判断程序是否处于调试状态。如VC下,调用了

BOOL WINAPI IsDebuggerPresent(VOID);
VOID WINAPI DebugBreak(VOID);



3. NDC(Nested Diagnostic Context)

3.1  概述

       NestedDiagnosticContext是为了多线程诊断而设计的。我们在写程序时,一般都需要同时处理多个线程。为了更加便捷的处理多线程情况,为每个线程产生各自的日志。Neil Harrison 在他的书中"Patterns for Logging Diagnostic Messages," in Pattern Languages of Program Design 3, edited by R.Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997) 中提出了一个方法。独特地标记每个日志请求,用户把上下文信息送入NDC,NDC是 Nested Diagnostic Context的缩写。在这本书里提到了3种日志方法,分别是:
        1. DiagnosticLogger
         分离日志和程序其他模块
        2. TransactionalBuckets
        事务桶,为事务单独建立日志
        3. TypedDiagnostics
        类型化诊断,为所有的诊断信息提供统一的展现


我们还是回到Poco中的NDC上。在Poco中和NDC相关的内容包括了,NestedDiagnosticContext类,NDCScope类,宏poco_ndc和poco_ndc_dbg。其中NestedDiagnosticContext类维护一个NDC对象,其中包括了上下文的栈信息,有函数方法名,源文件代码文件名,行号。宏poco_ndc(func) or poco_ndc_dbg(func)申明了一个NDCScope对象。而NDCScope对象则完成了上下文的入栈工作。下面是一个例子:


#include "Poco/NestedDiagnosticContext.h"
#include 
void f1()
{
            poco_ndc(f1);
            Poco::NDC::current().dump(std::cout);
}
void f2()
{
            poco_ndc(f2);
            f1();
}
int main(int argc, char** argv)
{
            f2();
            return 0;
}




3.2 实现

3.2.1 线程本地存储

       在Poco中实现时,用了一些小技巧,即线程本地存储。我们来看Poco中TLS的类图:




        CurrentThreadHolder类是TLS实现的具体类,在每个Thread对象中包含了一个CurrentThreadHolder对象。Thread创建的时候,CurrentThreadHolder会调用不同操作系统的API函数,获取并保存一个固定槽位,用于保存Thread对象的指针。
        每个Thread对象中还包含了一个ThreadLocalStorage对象。ThreadLocalStorage类用于保存具体的线程信息数据,它是一个TLSSlot对象的集合。通过泛型实现TLSSlot后,ThreadLocalStorage可用于保存任何数据的。

        使用了TLS技术后,调用Thread的静态函数current可以获取到每个线程对象Thread的指针,然后再通过这个Thread对象的指针,可以获取到ThreadLocalStorage对象,并最终获取或保存数据于TLSSlot中。

        通过类的静态函数获取类实例的指针,在C++中是不存在的,这需要操作系统支持,只有Thread对象才能做到这一点。




3.2.2 NDC

       在来看一张Poco中NDC类的类图:





        使用者通过调用宏poco_ndc和poco_ndc_dbg,来构建一个NDCScope对象。宏定义如下:

#define poco_ndc(func) \
            Poco::NDCScope _theNdcScope(#func, __LINE__, __FILE__)
 
#if defined(_DEBUG)
            #define poco_ndc_dbg(func) \
                        Poco::NDCScope _theNdcScope(#func, __LINE__, __FILE__)
#else
            #define poco_ndc_dbg(func)
#endif



        NDCScope实现了诊断信息上下文的入栈出栈工作,它通过调用NestedDiagnosticContext类的静态函数current实现了此功能。其定义如下:

inline NDCScope::NDCScope(const std::string& info)
{
            NestedDiagnosticContext::current().push(info);
}
 
inline NDCScope::NDCScope(const std::string& info, int line, const char* filename)
{
            NestedDiagnosticContext::current().push(info, line, filename);
}
 
inline NDCScope::~NDCScope()
{
            NestedDiagnosticContext::current().pop();
}


        NestedDiagnosticContext类的current()是个静态函数,其定义如下:

namespace
{
            static ThreadLocal ndc;
}
 
NestedDiagnosticContext& NestedDiagnosticContext::current()
{
            return ndc.get();
}



        而ThreadLocal是一个辅助类,用于获取线程对象的本地存储信息或者是主线程的本地存储信息。

template 
class ThreadLocal
            /// This template is used to declare type safe thread
            /// local variables. It can basically be used like
            /// a smart pointer class with the special feature
            /// that it references a different object
            /// in every thread. The underlying object will
            /// be created when it is referenced for the first
            /// time.
            /// See the NestedDiagnosticContext class for an
            /// example how to use this template.
            /// Every thread only has access to its own
            /// thread local data. There is no way for a thread
            /// to access another thread's local data.
{
            typedef TLSSlot Slot;
 
public:
            ThreadLocal()
            {
            }
            
            ~ThreadLocal()
            {
            }
            
            C* operator -> ()
            {
                        return &get();
            }
            
            C& operator * ()
                        /// "Dereferences" the smart pointer and returns a reference
                        /// to the underlying data object. The reference can be used
                        /// to modify the object.
            {
                        return get();
            }
 
            C& get()
                        /// Returns a reference to the underlying data object.
                        /// The reference can be used to modify the object.
            {
                        TLSAbstractSlot*& p = ThreadLocalStorage::current().get(this);
                        if (!p) p = new Slot;
                        return static_cast(p)->value();
            }
            
private:
            ThreadLocal(const ThreadLocal&);
            ThreadLocal& operator = (const ThreadLocal&);
};



        到这里Poco中所有的NDC流程都被打通了,用户终于可以实现按线程打印日志信息了。





附录:

1.  The Gregorian Calendar: http://en.wikipedia.org/wiki/Gregorian_calendar
2. The Julian Calendar:http://en.wikipedia.org/wiki/Julian_calendar
3. The Julian Day:http://en.wikipedia.org/wiki/Julian_day
4. Coordinated Universal Time (UTC):http://en.wikipedia.org/wiki/UTC
5.  ISO 8601:http://en.wikipedia.org/wiki/ISO_8601

 

POCO C++库学习和分析 --  随机数和数字摘要

 

           在程序设计时,有时候我们需要生成随机数和数字摘要。在Poco库中,也提供了上述功能,下面我们一一叙述:

1. 随机数生成

          Poco中生成随机数的类为Poco::Random类。它根据PRNG(pseudo random number generator )算法设计,采用了一个累加的非线性反馈算法。PRNG算法可以产生0 ~ 2^31之间的随机数整数。
           在接口上Poco::Random提供了一些函数,可以使使用者直接得到其他形式的随机数。如char, bool, float 和 double 类型。另外Poco库中还提供了RandomInputStream类,用于Poco::Random类的流操作。


成员函数:
           1. void seed(Poco::UInt32 seed)
           根据给定的种子值生成随机数。


           2. void seed()
           使用任意值(从RandomInputStream类中获取)生成随机数。


           3. 默认的构造时,Poco::Random类采用当前的时间和日期生成随机数。如果想要更好的随机效果,需要显式的调用seed()方法


           4. UInt32 next()
           返回0 ~ 2^31之间的随机整数


           5. UInt32 next(UInt32 n)
           返回0 ~ n之间的随机整数


           6. char nextChar()
           返回随机Char值


           7. bool nextBool()
           返回随机bool值


           8. float nextFloat()
           返回随机float值,范围0 ~ 1


           9. double nextDouble()
           返回随机double值,范围0 ~ 1


           下面是关于Random的一个例子:

#include "Poco/Random.h"
#include "Poco/RandomStream.h"
#include 
using Poco::Random;
using Poco::RandomInputStream;
int main(int argc, char** argv)
{
            Random rnd;
            rnd.seed();
            std::cout << "Random integer: " << rnd.next() << std::endl;
            std::cout << "Random digit: " << rnd.next(10) << std::endl;
            std::cout << "Random char: " << rnd.nextChar() << std::endl;
            std::cout << "Random bool: " << rnd.nextBool() << std::endl;
            std::cout << "Random double: " << rnd.nextDouble() << std::endl;
            RandomInputStream ri;
            std::string rs;
            ri >> rs;
            return 0;
}




2. 密码散列

          下面这段是Wiki上关于密码散列的介绍:
           A cryptographic hash function is ahash function with certain additional security properties to make itsuitable for use as a primitive in various information securityapplications, such as authentication and message integrity. Ahash function takes a long string (or message) of any length as inputand produces a fixed length string as output, sometimes termed a messagedigest or a digital fingerprint. Wikipedia


2.1 概述

          密码散列(cryptographic hash)是将目标文本转换成具有相同长度的、不可逆的杂凑字符串(或叫做消息摘要)。它有两个特点:
           1、哈希算法往往被设计成生成具有相同长度的文本
           2、哈希算法是不可逆的。(因为如果可逆,那么哈希就是世界上最强悍的压缩方式——能将任意大小的文件压缩成固定大小)

           密码散列是一个多对一映射,好的哈希算法应该对于输入的改变极其敏感。Poco中实现了被广泛使用的密码散列函数(cryptographic hash functions), 包括了MD4, MD5和SHA1。另外还提供了HMACEngine类实现了HMAC功能。HMAC全称为Hash-basedMessage Authentication Code,HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。



2.2 DigestEngine类

          Poco::DigestEngine类为所有的消息摘要类定义了通用接口。
           1. unsigned digestLength()
           用于获取不同消息摘要算法生成消息摘要的长度。
           2. const Digest& digest()
           获取消息摘要内容
           3. update(const void* data, unsignedlength)
           更新消息摘要内容


让我们来看一下DigestEngine类的类图。


下面是Poco中相关类的一些例子:


#include "Poco/HMACEngine.h"
#include "Poco/SHA1Engine.h"
using Poco::DigestEngine;
using Poco::HMACEngine;
using Poco::SHA1Engine;
int main(int argc, char** argv)
{
            std::string message1("This is a top-secret message.");
            std::string message2("Don't tell anyone!");
            std::string passphrase("s3cr3t"); // HMAC needs a passphrase
            HMACEngine hmac(passphrase); // we'll compute a HMAC-SHA1
            hmac.update(message1);
            hmac.update(message2);
            const DigestEngine::Digest& digest = hmac.digest();
            // finish HMAC computation and obtain digest
            std::string digestString(DigestEngine::digestToHex(digest));
            // convert to a string of hexadecimal numbers
            return 0;
}




2.3 与DigestEngine类相关的流(DigestInputStream/DigestOutputStream)

          可以通过Poco::DigestInputStream和Poco::DigestOutputStream类对DigestEngine类进行输入输出操作。过程很简单,只要在在构造Stream时,把相关的DigestEngine类传入即可。需要注意的是,在向DigestOutputStream类写入后,要及时调用flush函数,以确保Stream把所有数据都输入进DigestEngine类。

           下面是相关的一个例子:


#include "Poco/DigestStream.h"
#include "Poco/MD5Engine.h"
using Poco::DigestOutputStream;
using Poco::DigestEngine;
using Poco::MD5Engine;
int main(int argc, char** argv)
{
            MD5Engine md5;
            DigestOutputStream ostr(md5);
            ostr << "This is some text";
            ostr.flush(); // Ensure everything gets passed to the digest engine
            const DigestEngine::Digest& digest = md5.digest(); // obtain result
            std::string result = DigestEngine::digestToHex(digest);
            return 0;
}

 

POCO C++库学习和分析 -- 文件系统

 


               既然作为一个框架性的库,自然会提供对于文件系统的操作。在Poco库中,封装了一些类去完成上述操作。这些类包括了:
              1. Poco::Path

             2. Poco::File

             3. Poco::TemporaryFile

             4. Poco::DirectoryIterator

             5. Poco::Glob

              这些类在实现上并没有什么特殊的注意点,主要是不同操作系统API的调用。如果想学习API函数的话,确实是一个不错的例子。在这里将主要介绍这些类的接口和使用,主要以翻译Poco的使用文档为主。

1. Poco::Path

1.1 路径:

              1. 在不同操作系统中,指明文件和目录所在位置的标示符是不一样的。
               2. 标示符的不一致,会造成代码在不同平台之间移植的困难。
               3. Poco::Path类抽象了不同标识符之间的区别,使程序员可以把注意力集中在业务的开发上。
               4. Poco::Path类支持Windows、Unix、OpenVMS操作系统。



1.2 Poco路径简介:

              Poco中的路径包括了
               1. 一个可选的节点(node)名:
                             a) 在Windows上,这是计算机在UNC(UniversalNaming Convention)路径中的名字
                             b) 在OpenVMS中,这代表一个集群系统中的节点名
                             c) 在Unix中,此名字未被使用。
               2. 一个可选的设备(device)名:
                             a) 在Windows上,这是一个驱动器盘符
                             b) 在OpenVMS上,这是存储盘符的名字
                             c) 在Unix,此名字未被使用。
               3. 一个目录名的列表
               4. 一个文件名(包括扩展名)和版本号(OpenVMS特有)

               Poco支持两种路径:
               1. 绝对路径
               以根目录为起点的描述资源的目录
               2. 相对目录
               以某一个确定路径为起点的描述资源的目录(通常这是用户的当前目录)

               相对目录可以被转换为绝对目录(反之,并不成立)。
               在Poco中路径的指向可以是一个目录也可以是一个文件。当路径指向目录时,文件名为空。

               下面是Poco中关于路径的一些例子:

    Path: C:\Windows\system32\cmd.exe
        Style: Windows
        Kind: absolute, to file
        Node Name: –
        Device Name: C
        Directory List: Windows, system32
        File Name: cmd.exe
        File Version: –
 
    Path: Poco\Foundation\
        Style: Windows
        Kind: relative, to directory
        Node Name: –
        Device Name: –
        Directory List: Poco, Foundation
        File Name: –
        File Version: –
 
    Path: \\www\site\index.html
        Style: Windows
        Kind: absolute, to file
        Node Name: www
        Device Name: –
        Directory List: site
        File Name: index.html
        File Version: –
 
    Path: /usr/local/include/Poco/Foundation.h
        Style: Unix
        Kind: absolute, to file
        Node Name: –
        Device Name: –
        Directory List: usr, local, include, Poco
        File Name: index.html
        File Version: –
 
    Path: ../bin/
        Style: Unix
        Kind: relative, to directory
        Node Name: –
        Device Name: –
        Directory List: .., bin
        File Name: –
        File Version: –
 
    Path: VMS001::DSK001:[POCO.INCLUDE.POCO]POCO.H;2
        Style: OpenVMS
        Kind: absolute, to file
        Node Name: VMS001
        Device Name: DSK001
        Directory List: POCO, INCLUDE, POCO
        File Name: POCO.H
        File Version: 2




1.3 类说明

              1. Poco::Path类在Poco库中代表了路径。
               2. Poco::Path类并不关心路径所指向的目标在文件系统中是否存在。这个工作由Poco::File类负责。
               3. Poco::Path支持值语义(copy函数和赋值函数),但不支持关系操作符。

                构建一个路径
               构建一个路径存在着两种方式:
               1. 从0开始构建,分别构建node、device、directory、file
               2. 通过一个包含着路径的字符串去解析

               在构建时,可以指定路径的格式:
                  a)PATH_UNIX
                  b)PATH_WINDOWS
                   c)PATH_VMS
                  d)PATH_NATIVE (根据当前系统格式判断)
                  e)PATH_GUESS (让Poco库自行判断)


               从0构造路径
               1. 创建一个空路径,使用默认的构造函数(默认情况下路径格式为"相对目录")或者构造时使用一个bool参数去指定路径格式(true = absolute, false = relative)
               2. 如果需要的话,使用下列赋值函数去设置节点和设备名
                             void setNode(const std::string& node)
                             void setDevice(const std::string& device)
               3. 添加路径名
                             void pushDirectory(const std::string&name)
               4. 设置文件名
                             void setFileName(const std::string& name)

               下面是一个例子:

#include "Poco/Path.h"
int main(int argc, char** argv)
{
            Poco::Path p(true); // path will be absolute
            p.setNode("VMS001");
            p.setDevice("DSK001");
            p.pushDirectory("POCO");
            p.pushDirectory("INCLUDE");
            p.pushDirectory("POCO");
            p.setFileName("POCO.H");
            std::string s(p.toString(Poco::Path::PATH_VMS));
            // "VMS001::DSK001:[POCO.INCLUDE.POCO]POCO.H"
            p.clear(); // start over with a clean state
            p.pushDirectory("projects");
            p.pushDirectory("poco");
            s = p.toString(Poco::Path::PATH_WINDOWS); // "projects\poco\"
            s = p.toString(Poco::Path::PATH_UNIX); // "projects/poco/"
            s = p.toString(); // depends on your platform
            return 0;
}


               从一个字符串中解析路径名
               1. Poco支持从一个字符串中解析路径名
                             Path(const std::string& path)
                             Path(const std::string& path, Stylestyle)
               如果函数调用时,路径格式style不被指定,将使用当前系统路径格式。
               2. 可以从另一个路径(指向目录名)和文件名,或者两个路径(第一个为绝对路径,第二个为相对路径)构造
                             Path(const Path& parent, conststd::string& fileName)
                             Path(const Path& parent, const Path&relative)

               路径也可以通过下列函数去构建
                             Path& assign(const std::string& path)
                             Path& parse(const std::string& path)
                             Path& assign(const std::string& path,Style style)
                             Path& parse(const std::string& path,Style style)

               如果路径非法的话,会抛出Poco::PathSyntaxException异常。想要测试一个路径字符串是否合法,可以使用tryParse()函数:
                             bool tryParse(const std::string& path)
                             bool tryParse(const std::string& path,Style style)

               下面是一个例子:

#include "Poco/Path.h"
using Poco::Path;
int main(int argc, char** argv)
{
            //creating a path will work independent of the OS
            Path p("C:\\Windows\\system32\\cmd.exe");
            Path p("/bin/sh");
            p = "projects\\poco";
            p = "projects/poco";
            p.parse("/usr/include/stdio.h", Path::PATH_UNIX);
            bool ok = p.tryParse("/usr/*/stdio.h");
            ok = p.tryParse("/usr/include/stdio.h", Path::PATH_UNIX);
            ok = p.tryParse("/usr/include/stdio.h", Path::PATH_WINDOWS);
            ok = p.tryParse("DSK$PROJ:[POCO]BUILD.COM", Path::PATH_GUESS);
            return 0;
}


               Poco::Path类提供了函数用于转换成为字符串:
               std::string toString()
               std::stringtoString(Style style)
               当然也可以使用下列函数得到路径不同部分的字符串:
              const std::string&getNode()
              const std::string&getDevice()
              const std::string&directory(int n) (also operator [])
              const std::string&getFileName()
               可以调用下列函数获取目录的深度:
               int depth() const

               通过下面的函数可以得到和设置文件的基本名和扩展名:
                             std::string getBaseName() const
                             void setBaseName(const std::string&baseName)
                             std::string getExtension() const
                             void setExtension(const std::string&extension)

               下面是一个例子:

#include "Poco/Path.h"
using Poco::Path;
int main(int argc, char** argv)
{
            Path p("c:\\projects\\poco\\build_vs80.cmd", Path::PATH_WINDOWS);
            std::string device(p.getDevice()); // "c"
            int n = p.depth(); // 2
            std::string dir1(p.directory(0)); // "projects"
            std::string dir2(p[1]); // "poco"
            std::string fileName(p[2]); // "build_vs80.cmd"
            fileName = p.getFileName();
            std::string baseName(p.getBaseName()); // "build_vs80"
            std::string extension(p.getExtension()); // "cmd"
            p.setBaseName("build_vs71");
            fileName = p.getFileName(); // "build_vs71.cmd"
            return 0;
}


              路径操作:
               1. Path&makeDirectory()
               确保路径的结尾是一个目录名。如果原路径有文件名存在的话,添加一个与文件名同名的目录,并清除文件名。
               2. Path& makeFile()
               确保路径的结尾是一个文件名。如果原路径是一个目录名,则把最后一个目录名变成文件名,并去除最后一个目录名。
               3. Path&makeParent()
                   Pathparent() const
               使路径指向它的父目录(如果存在文件名的话,清除文件名;否则的话则移除最后一个目录名)
               4. Path&makeAbsolute()
                   Path&makeAbsolute(const Path& base)
                   Pathabsolute() const
                   Pathabsolute(const Path& base)
               转换相对路径为绝对路径
               5. Path&append(const Path& path)
               添加路径
               6. Path&resolve(const Path& path)
               如果新的路径为绝对路径,则代替现有的路径;否则则在原路径下追加

              路径属性:
               1. bool isAbsolute()const
               如果路径为绝对路径,返回true;否则为false
               2. bool isRelative()const
               如果路径为相对路径,返回true;否则为false
               3. bool isDirectory()const
               如果路径为目录,返回true;否则为false
               4. bool isFile() const
               如果路径为文件,返回true;否则为false


               下面是一个例子:

#include "Poco/Path.h"
using Poco::Path;
int main(int argc, char** argv)
{
            Path p("/usr/include/stdio.h", Path::PATH_UNIX);
            Path parent(p.parent());
            std::string s(parent.toString(Path::PATH_UNIX)); // "/usr/include/"
            Path p1("stdlib.h");
            Path p2("/opt/Poco/include/Poco.h", Path::PATH_UNIX);
            p.resolve(p1);
            s = p.toString(Path::PATH_UNIX); // "/usr/include/stdlib.h"
            p.resolve(p2);
            s = p.toString(Path::PATH_UNIX); // "/opt/Poco/include/Poco.h"
            return 0;
}



              特殊的目录和文件
               Poco::Path提供了静态函数,用于获取系统中的一些特殊目录和文件
               1. std::string current()
               返回当前的工作目录
               2. std::string home()
               返回用户的主目录
               3. std::string temp()
               返回操作系统的零时目录
               4. std::string null()
               返回系统的空目录(e.g.,"/dev/null" or "NUL:")

               下面是一个例子:

#include "Poco/Path.h"
#include 
using Poco::Path;
int main(int argc, char** argv)
{
            std::cout
                        << "cwd: " << Path::current() << std::endl
                        << "home: " << Path::home() << std::endl
                        << "temp: " << Path::temp() << std::endl
                        << "null: " << Path::null() << std::endl;
            return 0;
}


              路径和环境变量
               在配置文件中的路径经常包含了环境变量。在传递此类路径给Poco::Path之间必须对路径进行扩展。
               对包含环境变量的路径扩展可以使用如下函数
                              std::string expand(conststd::string& path)
               函数会返回一个对环境变量进行扩充后的路径名。环境变量的格式会根据操作系统有所不同。(e.g., $VAR on Unix, %VAR% on Windows).在Unix上,同样会扩展"~/"为当前用户的主目录。

               下面是一个例子:

#include "Poco/Path.h"
using Poco::Path;
int main(int argc, char** argv)
{
            std::string config("%HOMEDRIVE%%HOMEPATH%\\config.ini");
            // std::string config("$HOME/config.ini");
            std::string expConfig(Path::expand(config));
            return 0;
}


              文件系统主目录:

               voidlistRoots(std::vector& roots)
               会用所有挂载到文件系统的根目录来填充字符串数组。在windows上,为所有的驱动盘符。在OpenVMS上,是所有挂载的磁盘。在Unix上,为"/"。

              查询文件:
               bool find(conststd::string& pathList, const std::string& name, Path& path)
               在指定的目录集(pathList)中搜索指定名称的文件(name)。pathList参数中如果指定了多个查找目录,目录之间必须使用分割符 (Windows平台上为";" , Unix平台上为":")。参数name中可以包含相对路径。如果文件在pathList指定的目录集中存在,找到文件的绝对路径会被放入path参数中,并且函数返回true,否则函数返回false,并且path不改变。
               这个函数也存在一个使用字符串向量,而不是路径列表的迭代器的变体。定义如下:
               boolfind(StringVec::const_iterator it, StringVec::const_iterator end,const std::string& name, Path& path)

               下面是一个例子:

#include "Poco/Path.h"
#include "Poco/Environment.h"
using Poco::Path;
using Poco::Environment;
int main(int argc, char** argv)
{
            std::string shellName("cmd.exe"); // Windows
            // std::string shellName("sh"); // Unix
            std::string path(Environment::get("PATH"));
            Path shellPath;
            bool found = Path::find(path, shellName, shellPath);
            std::string s(shellPath.toString());
            return 0;
}




2. Poco::File

              同文件协同工作
               1. Poco仅支持与文件元数据协同工作。想要操作文件中的真实数据,需要使用标准库的文件流。
               2. 使用Poco库,你可以查出一个文件或者目录是否存在,是否可读或可写,文件何时创建和被修改,文件大小等信息。
               3. 通过Poco库,也可以修改文件属性,重命名文件,拷贝文件和删除文件。
               4. 通过Poco库,可以创建空文件(原子操作)和目录。

               所有同文件操作相关操作被定义于Poco::File中。为了创建一个Poco::File对象,需要提供一个路径作为参数。这个路径可以使用Poco::Path类,也可以是一个字符串。同时Poco::File也支持创建一个空对象,在稍后的操作中再设置路径。
               Poco::File支持所有的值语义,也支持所有的关系操作符 (==, !=, <, <=, >, >=)。关系操作符的结果是通过进行简单的文件路径比较而得到的。

               查询文件属性:
               1. bool exists() const
               如果文件存在返回true,否则为false
               2. bool canRead() const
               如果文件可读(用户对文件拥有足够权限)返回true,否则为false
               3. bool canWrite() const
               如果文件可写(用户对文件拥有足够权限)返回true,否则为false
               4. bool canExecute()const
               如果文件是可执行文件,返回true,否则为false
               5. bool isFile() const
               如果文件是常规文件(即不是目录或符号链接)返回为真,否则为false
               6. bool isLink() const
               如果文件是符号链接,返回为真,否则为false
               7. bool isDirectory()const
               如果文件是目录,返回为真,否则为false
               8. bool isDevice() const
               如果文件是设备,返回为真,否则为false
               9. bool isHidden() const
               如果文件属性为隐藏,返回为真,否则为false。(在Windows上文件属性为隐藏;Unit上使用".&qu

你可能感兴趣的:(7-2,C/C++)