POCO C++庫學習和分析 -- 序

POCO C++庫學習和分析 -- 序


1. POCO庫概述:

POCO是一個C++的開源庫集。同通常的C++庫相比,POCO的特色是提供了整一個應用框架。若是要作C++程序應用框架的快速開發,我以爲STL+boost+Poco+Qt+Mysql實在是個不錯的組合。html

下面的這張圖提供了POCO庫的一個結構。前端


對於POCO概述更加詳細的介紹,能夠看一下官方網站和《POCO C++庫導遊》以及《POCO C++簡介》這篇文章。java

 

對於我來講,POCO C++的可學習之處至少有如下幾點:node

1.      跨平臺庫的封裝linux

2.      Application的應用架構的模塊化。ios

3.      不一樣操做系統的底層API使用c++

4.      類的設計和設計模式的應用git

5.      泛型程序員


接下來的一系列文章就是我在學習時的一些體會。(本文對應的Poco庫代碼版本爲poco-1.4.2p1).web


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/

<POCOC++庫導遊>:http://hi.baidu.com/marsjin/item/1b0d86bb9f2e61f162388e30

<POCOC++簡介>: 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 <iostream>

#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 <iostream>

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 <iostream>

typedef Poco::ClassLoader<AbstractPlugin>PluginLoader;

typedef Poco::Manifest<AbstractPlugin>PluginManifest;

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<DWORD>(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<ThreadImpl*>(pThread));

#ifdefined(_DEBUG) && defined(POCO_WIN32_DEBUGGER_THREAD_NAMES)

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

#endif

      try

      {

           ThreadImpl* pTI =reinterpret_cast<ThreadImpl*>(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<ThreadImpl*>(pThread);  

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


最後用一段代碼實例來結束吧

1.  #include "Poco/Thread.h"  

2.  #include "Poco/Runnable.h"  

3.  #include <iostream>  

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<PooledThread*> 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<long>((_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<TimerExample>(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<iostream>

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<ActivityExample> _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<class ResultType, class ArgType, class OwnerType, class StarterType =ActiveStarter<OwnerType> >

classActiveMethod

{

// ...

}

其中ResultType表明了返回值的共性,ArgType表明了輸入參數的共性,OwnerType表明了業務調用擁有者的共性,StarterType表明了線程驅動的共性。類對象的入口函數則被包裝進入另外一個類ActiveRunnable中,其定義以下:

template <class ResultType, classArgType, class OwnerType>

class ActiveRunnable: publicActiveRunnableBase

{

// ...

}

讓咱們來看一個Active Methods的例子:

#include"Poco/ActiveMethod.h"

#include"Poco/ActiveResult.h"

#include <utility>

using Poco::ActiveMethod;

using Poco::ActiveResult;

class ActiveAdder

{

public:

     ActiveAdder():

       add(this, &ActiveAdder::addImpl)

     {}

     ActiveMethod<int, std::pair<int, int>, ActiveAdder> add;

private:

     int addImpl(const std::pair<int, int>& args)

     {

          return args.first + args.second;

     }

};

 

int main(int argc, char** argv)

{

     ActiveAdder adder;

     ActiveResult<int> 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<int> sum =adder.add(std::make_pair(1, 2));

 這行代碼是馬上返回的,sum此時並無獲得真實的運行結果。只有等到sum.wait()返回,真實的運算結果才被放置於sum中。
        正是因爲ActiveResult<int>sum = adder.add(std::make_pair(1, 2))馬上返回,才讓出了執行的主線程,讓多線程的威力顯現出來。這和把等待任務函數放在Activities中不一樣,ActiveMethod因爲有結果交換的過程,其等待函數放於結果返回值類ActiveResult<T>中。

2.      爲了傳入參數和傳出結果,經過泛型規範了其定義

ActiveMethod<int,std::pair<int, int>, ActiveAdder>

3. 深度封裝了線程,由於從調用方來看根本沒法看到線程的影子。沒有start,stop等函數. 
        第二點頗有意思,可以實現這一點,在於在ActiveMethod中重載了操做符().下面是其定義:

ActiveResultTypeoperator () (const ArgType& arg)

///Invokes the ActiveMethod.

{

      ActiveResultType result(newActiveResultHolder<ResultType>());

      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 <utility>

using Poco::ActiveMethod;

using Poco::ActiveResult;

class ActiveAdder: public Poco::ActiveDispatcher

{

public:

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

      {}

     ActiveMethod<int, std::pair<int, int>, ActiveAdder,

     Poco::ActiveStarter<Poco::ActiveDispatcher> > add;

private:

     int addImpl(conststd::pair<int, int>& args)

     {

         returnargs.first + args.second;

     }

};

 

int main(int argc, char** argv)

{

     ActiveAdder adder;

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

     sum.wait();

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

     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." <<std::endl;

                   pNf->release();

         }

};

 

int main(int argc, char** argv)

{

         Poco::TaskManagertm;

         ProgressHandlerpm;

         tm.addObserver(

                   Observer<ProgressHandler,Poco::TaskProgressNotification>

                   (pm,&ProgressHandler::onProgress)

                   );

         tm.addObserver(

                   Observer<ProgressHandler,Poco::TaskFinishedNotification>

                   (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<A> p1(new A);

     A* pA = p1;

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

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

     Poco::AutoPtr<A> 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<RCO> p1(pNew);                                  // _rc == 1

      AutoPtr<RCO> p2(p1);                                    // _rc == 2

      AutoPtr<RCO> 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>從原生指針C*構造時,AutoPtr獲取到對象C的全部權(對象的引用計數爲1,保持不變)。

        2. 當使用賦值操做符「=」把原生指針C*賦予AutoPtr<C>時,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<C>從另外一AutoPtr<C>構造時,兩個AutoPtr共享一個C的擁有權,引用計數增長

       4. 當使用賦值操做符「=」把AutoPtr<C>賦予另外一個AutoPtr<C>時,兩個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 <class Other>

AutoPtr<Other> 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> super(new Sub());

              ///   AutoPtr<Sub> sub = super.cast<Sub>();

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

{

              Other* pOther = dynamic_cast<Other*>(_ptr);

              return AutoPtr<Other>(pOther, true);

}

2. AutoPtr的cast老是安全的,由於其內部使用了dynamic_cast,所以若是轉換非法只會致使一個空指針。

 3. AutoPtr的賦值函數的兼容性經過模板構造函數和賦值操做符來支持。其定義以下:

template <class Other>

AutoPtr(constAutoPtr<Other>& ptr): _ptr(const_cast<Other*>(ptr.get()))

{

       if (_ptr) _ptr->duplicate();

}

template <class Other>

AutoPtr& assign(constAutoPtr<Other>& ptr)

{

       if (ptr.get() != _ptr)

       {

                     if (_ptr) _ptr->release();

                     _ptr = const_cast<Other*>(ptr.get());

                     if (_ptr) _ptr->duplicate();

       }

       return *this;

}

template <class Other>

AutoPtr& operator =(const AutoPtr<Other>& ptr)

{

       return assign<Other>(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<A> p1(new A);

         A* pA = p1;

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

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

         Poco::AutoPtr<A> 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 C, class RC =ReferenceCounter, class RP = ReleasePolicy<C> >

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操做符。這在 template<class Other>SharedPtr<Other> cast() const中實現,其定義以下:

template <class Other>

SharedPtr<Other, RC, RP> 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> super(new Sub());

         ///    SharedPtr<Sub> sub =super.cast<Sub>();

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

{

         Other*pOther = dynamic_cast<Other*>(_ptr);

         if(pOther)

                   returnSharedPtr<Other, RC, RP>(_pCounter, pOther);

         returnSharedPtr<Other, RC, RP>();

}

Poco::SharedPtr中類型轉換老是安全的,在其內部實現時,使用了dynamic_cast,因此一個不合法的轉換,會致使原生指針爲空。

       Poco::SharedPtr中賦值操做符的兼容性經過構造函數和賦值操做符共同完成。

template <class Other, class OtherRP>

SharedPtr& operator = (constSharedPtr<Other, RC, OtherRP>& ptr)

{

         returnassign<Other>(ptr);

}

 

template <class Other, class OtherRP>

SharedPtr& assign(const SharedPtr<Other,RC, OtherRP>& ptr)

{

         if(ptr.get() != _ptr)

         {

                   SharedPtrtmp(ptr);

                   swap(tmp);

         }

         return*this;

}

 

template <class Other, class OtherRP>

SharedPtr(const SharedPtr<Other, RC,OtherRP>& ptr): _pCounter(ptr._pCounter),_ptr(const_cast<Other*>(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::SharedPtr<A>pA;

         Poco::SharedPtr<B>pB(new B);

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

         pA= new B;

         //pB = pA;                     // will notcompile

         pB= pA.cast<B>();              //okay

         Poco::SharedPtr<C>pC(new C);

         pB= pC.cast<B>();              // pBis null

         return0;

}

 

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

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

對於Poco::SharedPtr來講,最好的方法是一旦用SharedPtr獲取到對象全部權後,就不要再試圖使用指向對象的原生指針。

      下面是SharedPtr的一個例子:

#include"Poco/SharedPtr.h"

#include <string>

#include <iostream>

using Poco::SharedPtr;

int main(int argc, char**argv)

{

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

              Poco::SharedPtr<std::string> p1(pString);                  // rc == 1

              Poco::SharedPtr<std::string> 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<C> pool;

              C* pC = new C;

              pool.add(pC);

              pC = new C;

              pool.add(pC);

              return 0;

}

// all C's deleted

其類圖以下:

                  

 在圖中能夠看出,AutoReleasePool實際上就是原生指針的一個容器,在其內部定義爲:std::list<C*> ObjectList _list

要注意的是,若是同時使用AutoReleasePool和AutoPtr對指針進行管理時,應該如此實現:

                   AutoReleasePool<C>arp;

AutoPtr<C> ptr = new C;

 ...

arp.add(ptr.duplicate());

很明顯此刻AutoReleasePool和AutoPtr對對象應該共享全部權。

5. 動態工廠模板(DynamicFactoryClass Template)

       Poco中實現了一個動態工廠的模板,支持經過類名來建立類。其實現技術和前面的文章"FoundationSharedLibrary模塊分析"中介紹的相似。

       動態工廠類DynamicFactory是抽象工廠類AbstractFactory的容器。

template <class Base>

class DynamicFactory

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

{

          ....

          std::map<std::string,AbstractFactory*> FactoryMap _map;

}

AbstractFactory實際上是模板類AbstractInstantiator<Base>的代稱,是典型的工廠模式,用來實現具體類的建立,每一個類工廠AbstractFactory對應一個類的建立。

template <class Base>

class DynamicFactory

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

{

public:

              typedef AbstractInstantiator<Base>AbstractFactory;

         ....

}

 而一個動態工廠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<C,Base>);

}

             

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_ptr<AbstractFactory>ptr(pAbstractFactory);

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

              if (it == _map.end())

                            _map[className] = ptr.release();

              else

                            throw ExistsException(className);

}

接下來再來看DynamicFactory類中的建立工做:

template <class Base>

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<std::string,AbstractFactory*>::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<Base> factory;

              factory.registerClass<A>("A"); //creates Instantiator<A, Base>

              factory.registerClass<B>("B"); //creates Instantiator<B, Base>

              SharedPtr<Base> pA =factory.createInstance("A");

              SharedPtr<Base> 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<Base>

{

public:

              CInstantiator(int i): _i(i)

              {}

              Base* createInstance() const

              {

                            return new C(_i);

              }

private:

              int _i;

};

 

int main(int argc, char**argv)

{

              DynamicFactory<Base> factory;

              factory.registerClass<A>("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<char*> BlockVec;

             

              std::size_t _blockSize;

              int        _maxAlloc;

              int        _allocated;

              BlockVec    _blocks;

              FastMutex  _mutex;

};

其中_maxAlloc是內存池可分配的最大內存塊數,_blockSize是每一個內存塊的大小。

 

      下面是內存池的一個例子:

#include"Poco/MemoryPool.h"

#include <string>

#include <iostream>

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<char*>(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 S>

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<std::string>& 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 <fstream>

using Poco::Process;

using Poco::ProcessHandle;

int main(int argc, char** argv)

{

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

              std::vector<std::string> 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<Notification> 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<AbstractObserver> AbstractObserverPtr;  

19.      typedef std::vector<AbstractObserverPtr> ObserverList;  

20.    

21.      ObserverList  _observers;  

22.      mutable Mutex _mutex;  

23.  };  


        從定義能夠看出它是一個目標對象的集合std::vector<SharedPtr<AbstractObserver>>_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<Mutex> 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<N> &);  

        函數調用時分別帶了一個參數。這其實解決了全部的問題,由於一個參數能夠是結構體,供使用傳入傳出所需的值。固然咱們也能夠從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<N*>(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<N*>(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 <iostream>  

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<SubNotification>& 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, BaseNotification>(target, &Target::handleBase)  

41.          );  

42.      nc.addObserver(  

43.          NObserver<Target, SubNotification>(target, &Target::handleSub)  

44.          );  

45.      nc.postNotification(new BaseNotification);  

46.      nc.postNotification(new SubNotification);  

47.      nc.removeObserver(  

48.          Observer<Target, BaseNotification>(target, &Target::handleBase)  

49.          );  

50.      nc.removeObserver(  

51.          NObserver<Target, SubNotification>(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<Notification> 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<AbstractObserver> AbstractObserverPtr;  

19.      typedef std::vector<AbstractObserverPtr> ObserverList;  

20.    

21.      ObserverList  _observers;  

22.      mutable Mutex _mutex;  

23.  };  


        從定義能夠看出它是一個目標對象的集合std::vector<SharedPtr<AbstractObserver>>_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<Mutex> 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<N> &);  

        函數調用時分別帶了一個參數。這其實解決了全部的問題,由於一個參數能夠是結構體,供使用傳入傳出所需的值。固然咱們也能夠從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<N*>(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<N*>(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 <iostream>  

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<SubNotification>& 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, BaseNotification>(target, &Target::handleBase)  

41.          );  

42.      nc.addObserver(  

43.          NObserver<Target, SubNotification>(target, &Target::handleSub)  

44.          );  

45.      nc.postNotification(new BaseNotification);  

46.      nc.postNotification(new SubNotification);  

47.      nc.removeObserver(  

48.          Observer<Target, BaseNotification>(target, &Target::handleBase)  

49.          );  

50.      nc.removeObserver(  

51.          NObserver<Target, SubNotification>(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<Notification::Ptr> NfQueue;  

106.     struct WaitInfo  

107.     {  

108.         Notification::Ptr pNf;  

109.         Event             nfAvailable;  

110.     };  

111.     typedef std::deque<WaitInfo*> 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<Notification> pNf(_queue.waitDequeueNotification());  

31.          while (pNf)  

32.          {  

33.              WorkNotification* pWorkNf =  

34.                  dynamic_cast<WorkNotification*>(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 <iostream>  

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<T>,目標對象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<TArgs>  

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<TArgs>(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<TArgs>& 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<TArgs>* 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 Delegate<TObj, TArgs, true> delegate(TObj* pObj, void (TObj::*NotifyMethod)(const void*, TArgs&))  

3.   {  

4.       return Delegate<TObj, TArgs, true>(pObj, NotifyMethod);  

5.   }  


        在來看DefaultStrategy類,其定義以下:

[cpp] view plaincopy

1.   template <class TArgs, class TDelegate>   

2.   class DefaultStrategy: public NotificationStrategy<TArgs, TDelegate>  

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<TDelegate>         DelegatePtr;  

11.      typedef std::vector<DelegatePtr>     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<TDelegate*>(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<SharedPtr<TDelegate>>  _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<TArgs, AbstractDelegate<TArgs> >,  

4.       AbstractDelegate<TArgs>,  

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<TMutex> lock(_mutex);  

74.            

75.          if (!_enabled) return;  

76.            

77.          TStrategy strategy(_strategy);  

78.          lock.unlock();  

79.          strategy.notify(pSender, args);  

80.      }  

81.    

82.      ActiveResult<TArgs> 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<TStrategy>(new TStrategy(_strategy));  

89.              params.enabled  = _enabled;  

90.          }  

91.          ActiveResult<TArgs> result = _executeAsync(params);  

92.          return result;  

93.      }  

94.          // .......  

95.    

96.  protected:  

97.      struct NotifyAsyncParams  

98.      {  

99.          SharedPtr<TStrategy> 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<TArgs, NotifyAsyncParams, AbstractEvent> _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 <iostream>  

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<RT> AsyncFireEvent(int n)  

25.      {  

26.          return ActiveResult<RT> (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 <iostream>  

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 <assert>  

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支持Int八、Int1六、Int3二、Int64UInt八、UInt1六、UInt3二、UInt6四、bool、float、double、char、std::string、long、unsigned long、std::vector<T>、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<T>* pHolderImpl = static_cast<DynamicAnyHolderImpl<T>*>(_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<MyType*>(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<Any::Holder<ValueType>*>(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_cast<std::size_t>(n)*2654435761U;   

4.   }  

5.     

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

7.   {  

8.       return static_cast<std::size_t>(n)*2654435761U;   

9.   }  

10.    

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

12.  {  

13.      return static_cast<std::size_t>(n)*2654435761U;   

14.  }  

15.    

16.    

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

18.  {  

19.      return static_cast<std::size_t>(n)*2654435761U;   

20.  }  

21.    

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

23.  {  

24.      return static_cast<std::size_t>(n)*2654435761U;   

25.  }  

26.    

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

28.  {  

29.      return static_cast<std::size_t>(n)*2654435761U;   

30.  }  

31.    

32.    

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

34.  {  

35.      return static_cast<std::size_t>(n)*2654435761U;   

36.  }  

37.    

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

39.  {  

40.      return static_cast<std::size_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 (kpv@research.att.com) and 

36.  * Glenn Fowler (gsf@research.att.com). Landon Curt Noll (chongo@toad.com) 

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<HashEntry*> _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<vector<Value>>,同時還存在兩個控制量_split和_front:

[cpp] view plaincopy

1.   std::size_t _split;  

2.   std::size_t _front;  

3.   vector<vector<Value>> _buckets;  


        它的插入操做以下:

[cpp] view plaincopy

1.   std::pair<Iterator, bool> 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::pair<IntMap::Iterator, bool> 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::pair<IntMap::Iterator, bool> 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<TKey, TValue >, TEventMutex > Add;  

7.       FIFOEvent<const KeyValueArgs<TKey, TValue >, 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<TKey, SharedPtr<TValue > > DataHolder;  

13.      typedef typename DataHolder::iterator       Iterator;  

14.      typedef typename DataHolder::const_iterator ConstIterator;  

15.      typedef std::set<TKey>                      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<ValidArgs<TKey> > IsValid;  

36.      mutable FIFOEvent<KeySet>           Replace;  

37.    

38.      void initialize()  

39.          /// Sets up event registration.  

40.      {  

41.          Add     += Delegate<TStrategy, const KeyValueArgs<TKey, TValue> >(&_strategy, &TStrategy::onAdd);  

42.          Update  += Delegate<TStrategy, const KeyValueArgs<TKey, TValue> >(&_strategy, &TStrategy::onUpdate);  

43.          Remove  += Delegate<TStrategy, const TKey>(&_strategy, &TStrategy::onRemove);  

44.          Get     += Delegate<TStrategy, const TKey>(&_strategy, &TStrategy::onGet);  

45.          Clear   += Delegate<TStrategy, const EventArgs>(&_strategy, &TStrategy::onClear);  

46.          IsValid += Delegate<TStrategy, ValidArgs<TKey> >(&_strategy, &TStrategy::onIsValid);  

47.          Replace += Delegate<TStrategy, KeySet>(&_strategy, &TStrategy::onReplace);  

48.      }  

49.    

50.      void uninitialize()  

51.          /// Reverts event registration.  

52.      {  

53.          Add     -= Delegate<TStrategy, const KeyValueArgs<TKey, TValue> >(&_strategy, &TStrategy::onAdd );  

54.          Update  -= Delegate<TStrategy, const KeyValueArgs<TKey, TValue> >(&_strategy, &TStrategy::onUpdate);  

55.          Remove  -= Delegate<TStrategy, const TKey>(&_strategy, &TStrategy::onRemove);  

56.          Get     -= Delegate<TStrategy, const TKey>(&_strategy, &TStrategy::onGet);  

57.          Clear   -= Delegate<TStrategy, const EventArgs>(&_strategy, &TStrategy::onClear);  

58.          IsValid -= Delegate<TStrategy, ValidArgs<TKey> >(&_strategy, &TStrategy::onIsValid);  

59.          Replace -= Delegate<TStrategy, KeySet>(&_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<TKey, TValue> args(key, val);  

71.          Add.notify(this, args);  

72.          _data.insert(std::make_pair(key, SharedPtr<TValue>(new TValue(val))));  

73.            

74.          doReplace();  

75.      }  

76.    

77.      void doAdd(const TKey& key, SharedPtr<TValue>& 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<TKey, TValue> 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<TKey, TValue> 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<TValue>(new TValue(val))));  

102.         }  

103.         else  

104.         {  

105.             Update.notify(this, args);  

106.             it->second = SharedPtr<TValue>(new TValue(val));  

107.         }  

108.           

109.         doReplace();  

110.     }  

111.   

112.     void doUpdate(const TKey& key, SharedPtr<TValue>& 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<TKey, TValue> 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<TKey> args(key);  

154.             IsValid.notify(this, args);  

155.             result = args.isValid();  

156.         }  

157.   

158.         return result;  

159.     }  

160.   

161.     SharedPtr<TValue> 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<TValue> 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<TKey> 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<TKey> delMe;  

199.         Replace.notify(this, delMe);  

200.         // delMe contains the to be removed elements  

201.         typename std::set<TKey>::const_iterator it    = delMe.begin();  

202.         typename std::set<TKey>::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<TKey, SharedPtr<TValue > > _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<TValue>& 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<TKey, TValue> 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中保存的<key-value>pair中key的排序工做。每一個Strategy中都存在一個key的容器,其中LRUStrategy中是std::list<TKey>,ExpireStrategy、UniqueAccessExpireStrategy、UniqueExpireStrategy中是std::multimap<Timestamp, TKey>。

       對於LRU策略,這麼設計我是能夠理解的。每次訪問都會使key被重置於list的最前端。爲了實現對list快速訪問,增長一個std::map<TKey, Iterator>容器,每次對list容器進行插入操做時,把插入位的itorator保存入map中,這樣對於list的訪問效率能夠從O(n)變成O(log(n)),由於不須要遍歷了。下面是相關的代碼:

[cpp] view plaincopy

1.   void onReplace(const void*, std::set<TKey>& 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<TKey,IndexIterator>,另一個是std::multimap<Timestamp, TKey>。進行插入操做時,代碼爲:

[cpp] view plaincopy

1.   void onAdd(const void*, const KeyValueArgs <TKey, TValue>& args)  

2.   {  

3.       Timestamp now;  

4.       IndexIterator it = _keyIndex.insert(typename TimeIndex::value_type(now, args.key()));  

5.       std::pair<Iterator, bool> 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<TKey>& 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<TKey,IndexIterator>和std::multimap<Timestamp, TKey>合二爲一,定義成爲std::map<TKey, Timestamp>,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<std::string> 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-二、UCS-四、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-八、UTF-七、UTF-1六、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-八、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<TextEncoding> 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 <iostream>  

13.  #include <assert.h>  

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 <iostream>  

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 <ctime>  

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年,是因2八、1九、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 <iostream>
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 <iostream>
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<NestedDiagnosticContext> ndc;
}
 
NestedDiagnosticContext& NestedDiagnosticContext::current()
{
            return ndc.get();
}



        而ThreadLocal是一個輔助類,用於獲取線程對象的本地存儲信息或者是主線程的本地存儲信息。

template <class C>
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<C> 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<Slot*>(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 <iostream>
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)是將目標文本轉換成具備相同長度的、不可逆的雜湊字符串(或叫作消息摘要)。它有兩個特色:
           一、哈希算法每每被設計成生成具備相同長度的文本
           二、哈希算法是不可逆的。(由於若是可逆,那麼哈希就是世界上最強悍的壓縮方式——能將任意大小的文件壓縮成固定大小)

           密碼散列是一個多對一映射,好的哈希算法應該對於輸入的改變極其敏感。Poco中實現了被普遍使用的密碼散列函數(cryptographic hash functions), 包括了MD4, MD5SHA1。另外還提供了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<SHA1Engine> 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 <iostream>
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<std::string>& 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上使用"."開頭的文件)
               10. Poco::Timestampcreated() const
               返回文件建立的日期和時間
               11. Poco::TimestampgetLastModified() const
               返回最後訪問文件的時間和日期
               12. File::FileSizegetSize() const
               返回文件大小(單位爲字節)。在大多數系統中,File::FileSize被定義成一個unsigned64-bit整數。

              修改文件屬性
               1. voidsetLastModified(Poco::Timestamp dateTime)
               設置最後訪問文件的時間
               2. void setSize(FileSizenewSize)
               設置文件大小(單位爲字節)。可用於截斷一個文件。
               3. void setWritable(boolflag = true)
               若是flag== true,使文件可寫;flag == false至關於文件只讀
               4. void setReadOnly(boolflag)
               同setWritable(!flag)做用相同

              重命名,拷貝和刪除
               1. void copyTo(const std::string&path) const
               拷貝文件至指定目錄(一般是個文件夾)
               2. void moveTo(conststd::string& path) const
               拷貝文件至指定目錄(一般是個文件夾)而且刪除源文件
               3. void renameTo(conststd::string& path)
               重命名文件
               4. void remove(boolrecursive = false)
               刪除文件。若是文件是個目錄,而且recursive == true,則遞歸的刪除全部文件和子目錄。
               
              建立文件和目錄
               1. bool createFile()
               使用原子操做建立一個新的空文件。若是文件被建立返回true,若是文件已存在返回false。若是建立失敗,拋出Poco::FileException異常。
               2. boolcreateDirectory()
               建立一個新目錄。若是目錄被建立,返回true,若是目錄已存在,返回false。若是建立失敗,拋出Poco::FileException(好比說父目錄不存在)。
               3. voidcreateDirectories()
               建立目錄集。當父目錄不存在時,也會同時建立父目錄。

              讀取目錄
               1. voidlist(std::vector<std::string>& files) const
                   voidlist(std::vector<File>& files) const
               會把目錄中全部的文件名填入給定的files數組。在其內部,會使用Poco::DirectoryIterator遍歷目錄

               下面是一個例子:

#include "Poco/File.h"
#include "Poco/Path.h"
#include <iostream>
using Poco::File;
using Poco::Path;
int main(int argc, char** argv)
{
            std::string tmpPath(Path::temp());
            tmpPath.pushDirectory("PocoFileSample");
            File tmpDir(tmpPath);
            tmpDir.createDirectories();
            bool exists = tmpDir.exists();
            bool isFile = tmpDir.isFile();
            bool isDir = tmpDir.isDirectory();
            bool canRead = tmpDir.canRead();
            bool canWrite = tmpDir.canWrite();
            File tmpFile(Path(tmpPath, std::string("PocoFileSample.dat")));
            if (tmpFile.createFile())
            {
                        tmpFile.setSize(10000);
                        File tmpFile2(Path(tmpPath, std::string("PocoFileSample2.dat")));
                        tmpFile.copyTo(tmpFile2.path());
                        Poco::Timestamp now;
                        tmpFile.setLastModified(now);
                        tmpFile.setReadOnly();
                        canWrite = tmpFile.canWrite();
                        tmpFile.setWriteable();
                        canWrite = tmpFile.canWrite();
            }
            std::vector<std::string> files;
            tmpDir.list(files);
            std::vector<std::string>::iterator it = files.begin();
            for (; it != files.end(); ++it)
            {
                        std::cout << *it << std::endl;
            }
            tmpDir.remove(true);
            return 0;
}



3. DirectoryIterator類

              Poco::DirectoryIterator類爲讀取目錄中的內容提供了一個迭代子風格的接口。
               Poco::DirectoryIterator使用時有以下限制:
               1. 僅支持前向迭代操做(forwarditeration (++))
               2. 當一個迭代子拷貝自另外一個迭代子時,老是指向源迭代子的曾經指向的文件。即便源迭代子已經指向另一個文件。
               3.Poco::DirectoryIterator內部維護一個Poco::File和Poco::Path絕對路徑的對象。

               下面是一個例子:

#include "Poco/DirectoryIterator.h"
#include <iostream>
using Poco::DirectoryIterator;
using Poco::Path;
int main(int argc, char** argv)
{
            std::string cwd(Path::current());
            DirectoryIterator it(cwd);
            DirectoryIterator end;
            while (it != end)
            {
                        std::cout << it.name();
                        if (it->isFile())
                                     std::cout << it->getSize();
                        std::cout << std::endl;
                        Path p(it.path());
                        ++it;
            }
            return 0;
}




4. Glob類

              Poco::Glob支持同Unixshells相似的通配符匹配。'*'匹配任何字符串序列,'?'匹配任意單一文件,[SET]匹配任意存在於指定字符集中的單一字符,[!SET]匹配任何不存在於指定字符集中的單一字符。[SET]能夠指定字符集的範圍和鎖包含的字符。例如[123]用來匹配數字1,2,3;[a-zA-Z]匹配任何大寫或小寫英文字符。Glob類支持用一個反斜槓轉義的特殊字符。
               Poco::Glob能夠使用一個pattern,或者一個選項的標誌位去建立。GLOB_DOT_SPECIAL選項適用於Unix隱藏文件。

              內部函數:
               1. bool match(conststd::string& subject)
               若是subject對應的路徑符合匹配規則,返回true,不然爲false
               2. void glob(conststd::string& pattern, std::set<std::string>& files, int options =0)
               void glob(constPath& pattern, std::set<std::string>& files, int options = 0)
               用符合給定pattern的全部文件填充給定的set集

               下面是一個例子:

#include "Poco/Glob.h"
#include <iostream>
using Poco::Glob;
int main(int argc, char** argv)
{
            std::set<std::string> files;
            Glob::glob("%WINDIR%\\system32\\*.exe", files);
            // Glob::glob("/usr/include/*/*.h", files);
            std::set<std::string>::iterator it = files.begin();
            for (; it != files.end(); ++it)
            {
                        std::cout << *it << std::endl;
            }
            return 0;
}




5. 臨時文件

              寫程序時,有時候須要用到臨時文件,臨時文件的使用有一下特色:
               1. 臨時文件只在指定的目錄被建立。如Unix系統上的"/tmp/"目錄
               2. 臨時文件會自動生成惟一名字
               3. 臨時文件在再也不被使用時,須要被刪除

               Poco::TemporaryFile中也提供了一個類來實現此功能。


5.1 Poco::TemporaryFile類

              1. Poco::TemporaryFile繼承自Poco::File
               2. 構造函數會爲臨時文件自動生成一個惟一的文件名,以及放置臨時文件的操做系統默認目錄。固然文件自己並未被建立。
               3. 若是臨時文件被建立,析構函數會刪除該文件
               4. 二選一的,刪除動做能夠被延遲到程序結束或者再也不被使用。
               5. 任何文件均可以被延遲到程序終止時才被刪除。

              成員函數:
               1. void keep()
               中止析構函數中刪除文件的動做
               2. void keepUntilExit()
               中止析構函數中刪除文件的動做,並註冊該文件,延遲到程序結束時刪除
               3. static voidregisterForDeletion(const std::string& path)
               註冊文件,使之在程序結束時刪除
               4. static std::stringtempName()
               爲臨時文件建立一個惟一的名字

               下面是一個例子:

#include "Poco/TemporaryFile.h"
#include <fstream>
using Poco::TemporaryFile;
int main(int argc, char** argv)
{
            TemporaryFile tmp;
            std::ofstream ostr(tmp.path().c_str());
            ostr << "Hello, world!" << std::endl;
            ostr.close();
            return 0;
}
 

POCO C++庫學習和分析 -- 日誌 (一)

 

         日誌對於程序來講是很是重要的,特別是對一些大型程序而言。一旦程序被髮布,在現場日誌幾乎是程序員惟一能夠獲取程序信息的手段。Poco做爲一個框架類庫,提供了很是多的日誌種類供程序員選用。文章將分兩個部分,對於Poco日誌進行介紹。第一部分主要以翻譯Poco文檔爲主,第二部分則探討Poco日誌的實現。

1. Poco庫日誌接口

1.1  整體介紹 

        Poco中的日誌模塊主要涉及下列幾個部分。
          1. 消息,日誌和通道
          2. 格式
          3. 執行效率的考量

          模塊框架圖:

 

 

 

 

1.2  消息(Message類):

          1. 全部的消息都被存儲並經過類Poco::Message傳遞
          2. 一個消息包括了下述特性:
            a. 優先級
            b. 消息源
            c. 消息內容
            d. 時間戳
            e. 進程與線程標記
            f. 可選參數(名字-值)對

          消息優先級:
          Poco定義了8種消息優先級:
               PRIO_FATAL
               PRIO_CRITICAL
               PRIO_ERROR
               PRIO_WARNING
               PRIO_NOTICE
               PRIO_INFORMATION
               PRIO_DEBUG
               PRIO_TRACE
          能夠經過函數設置和獲取消息優先級:

                void setPriority(Priority prio)
                Priority getPriority() const


          消息源:
          消息源用來描述日誌消息的源。一般狀態下,使用Poco::Logger的名字來命名。所以應該合理的命名Poco::Logger的名字。
          能夠經過函數設置和獲取消息源:

                void setSource(const std::string& source)
                const std::string& getSource() const 


   
          消息內容:
          在Poco中消息內容是不考慮格式和長度等問題的,只是消息內容。當消息最終輸出時,消息內容有可能被類Poco::formatter修改。
          能夠經過函數設置和獲取消息內容:

                void setText(const std::string& text)
                const std::string& getText() const


          消息時間戳:
          記錄消息產生時的時間戳,精度爲毫秒。
          能夠經過函數設置和獲取時間戳: 

                void setTime(const Timestamp& time)
                const Timestamp& getTime() const


          進程和線程標識符:
          進程標識符(PID)爲長整形的int值,用來存儲系統的進程ID。
          線程標識符(TID)一樣爲長整形的int值,用於存儲當前線程的ID值。
          一樣的當前線程的名字也會被存儲。進程標識符(PID)、線程標識符(TID)、線程名在Poco::Message初始化時會自動生成。
          能夠使用下列函數對進程標識符(PID)、線程標識符(TID)、線程名進行操做:

             void setThread(const std::string& threadName)
             const std::string& getThread() const
             void setTid(long tid)
             long getTid() const
             void setPid(long pid)
             long getPid() const


          消息參數:
          一個消息能夠存儲任意數目的name-value對 。
         name-value能夠是任意字符串。
          消息參數能夠被用於最終的格式輸出。
          消息參數支持下標索引。


1.3 Logger類:

          應用程序能夠使用Poco::Logger類去產生日誌消息。每個日誌對象內部都包含了一個通道對象(Channel),通道用於最終把消息送到目的地。
          每個logger對象都有名字,logger對象的名字會被用於命名全部由此對象產生的消息的消息源名稱。名字一旦被設定,將不能被改變。
          每個Poco::Logge對象都有其本身的優先級。有了優先級後,Poco::Logge對象即可以對消息進行過濾。只有消息的優先級比Poco::Logge對象的優先級高,消息纔會被Poco::Logge對象所傳遞。

          Logger的繼承體系。
          1. 基於Logger的名字,能夠造成日誌的樹狀繼承體系。
          2. 一個Logger對象的名字包含了一個或多個部分,不一樣部分之間使用'.'分隔。每一個日誌組件的名稱都包含了上級日誌組件的名稱
          3. 存在一個特殊的Logger,即root Logger,其名字爲空。它是全部Logger的根。
          4. 對於Logger繼承的深度Poco庫並無限制。

          下面是對於Logger繼承的一個說明:
          LoggerHierarchy Example
            |
            |---- "" (the root logger)
               |
               |-----"HTTPServer"
                   |
                   |-----"HTTPServer.RequestHandler"
                   |
                   |-----"HTTPServer.RequestHandler.File"
                   |
                   |-----"HTTPServer.RequestHandler.CGI"
                   |
                   |------"HTTPServer.Listener"

          說明:
         1. 一個新的logger將繼承它的上級日誌組件的級別和通道。好比說,上例中"HTTPServer.RequestHandler.CGI"會繼承"HTTPServer.RequestHandler"的日誌級別和通道。
          2. 一旦一個logger被徹底建立,它就將與它的上級無關。徹底建立指,logger擁有本身的channel和日誌級別,而不是和其它logger共用。換句話說,改變日誌級別和通道將不會影響的到其餘的已經存在的logger對象。
          3. 儘量的對日誌對象一次設置全部的參數,好比說日誌級別和通道。

          記錄消息:
          1. voidlog(const Message& msg)
          若是消息的優先級高於或者等於logger的優先級,消息將被傳遞到logger對應的通道中。消息傳遞時並不會發生改變。
          2. voidlog(const Exception& exc)
          使用最高優先級PRIO_ERROR,建立並記錄消息。消息內容爲異常內容。
          3. 使用下列不一樣優先級和給定的文字建立並記錄消息

       void fatal(const std::string& text)
       void critical(const std::string& text)
       void error(const std::string& text)
       void warning(const std::string& text)   
       void notice(const std::string& text)
       void information(const std::string& text)
       void debug(const std::string& text)
       void trace(const std::string& text)

          4. 使用給定的優先級和內容記錄消息。消息的內容爲16進制的給定Dump數據塊。
            Logging Messages (cont'd)
          5. 判斷日誌等級
            bool is(int level) const
            若是logger的日誌級別等於或高於查詢的日誌級別,返回true

       bool critical() const
       bool error() const
       bool warning() const
       bool notice() const
       bool information() const
       bool debug() const
       bool trace() const
       bool fatal() const

            若是logger的日誌級別等於或高於給定的日誌級別,返回true

          訪問日誌對象:
          POCO庫在內部管理了一個全局的日誌map。用戶不須要本身建立logger對象,用戶能夠向POCO庫申請一個logger對象的引用。POCO會根據須要建立新的日誌對象。
          staticLogger& get(const std::string& name)
          使用上面函數能夠獲取到給定名稱所關聯的logger對象的引用,若是有必要,POCO庫會在內部建立一個logger對象。出於效率上的考慮,Poco使用文檔推薦用戶保存所使用的logger對象的引用,而不是頻繁的調用此函數。理所固然的,POCO庫能保證logger對象的引用始終有效。

          下面是一個例子:

#include "Poco/Logger.h"
using Poco::Logger;
int main(int argc, char** argv)
{
          Logger& logger = Logger::get("TestLogger");
          logger.information("This is an informational message");
          logger.warning("This is a warning message");
          return 0;
}



1.4 通道:

          通道的子類負責傳遞消息給最終目的地。好比說控制檯或者日誌文件等。
          每個 Poco::Logger類對象(它自己也是Poco::Channel的子類)都對應着一個Poco::Channel類對象。在Poco庫內部已經實現了各類Poco::Channel子類,用於向不一樣的目標輸出日誌,好比說控制檯,日誌文件,或者系統日誌工具。用戶能夠定義本身的channel類。在內部Poco::Channel使用了引用計數技術來實現內存管理。

          通道屬性:
          通道支持配置任意數目的屬性,屬性爲一個名字值對。屬性能夠經過如下函數獲取和設置:

             void setProperty(const std::string& name, const std::string& value)
             std::string getProperty(const sdt::string& name)

          這兩個函數被定義在Poco::Configurable中,Poco::Configurable爲Poco::Channel的父類。


1.4.1 控制檯通道(ConsoleChannel)

          Poco::ConsoleChannel能夠知足大多數的控制檯輸出。它只是簡單的把消息內容寫入了標準輸出流(std::clog),而且不支持配置屬性。它是根logger默認關聯的通道(貌似這裏有點誤解,根logger並不會自動建立ConsoleChannel)。


1.4.2 windows控制檯通道(WindowsConsoleChannel)

          Poco::WindowsConsoleChannel同ConsoleChannel相似,惟一不一樣的是向windows控制檯輸出。它只是簡單把消息內容寫入window控制檯,而且不支持配置屬性。向window控制檯輸出時,支持UTF-8編碼。


1.4.3 空白通道(NullChannel)

          Poco::NullChannel通道會拋棄全部發向它的消息,而且忽略全部setProperty()函數設置的屬性。


1.4.4 簡單文件通道(SimpleFileChannel)

          Poco::SimpleFileChannel類實現了向日志文件輸出的簡單功能。對於每個消息,其內容都會被添加到文件中,並使用一個新行輸出。簡單日誌文件支持文件循環覆蓋,一旦主日誌文件超過肯定的大小,第二個日誌文件會被建立,若是第二個日誌文件已經存在,會被截斷。而當第二個日誌文件超過大小限制,主日誌文件將被覆蓋。如此循環。

          簡單文件通道屬性
         path:
 主日誌文件路徑
          secondaryPath: 第二個日誌文件路徑。默認同主日誌文件路徑。
          rotation:日誌循環覆蓋模式。能夠有如下幾種選擇:
              never: 不須要循環覆蓋
              <n>: 若是超過 <n> 字節的話,循環覆蓋
              <n> K: 若是超過 <n> K字節的話,循環覆蓋
              <n> M: 若是超過 <n> M字節的話,循環覆蓋

          下面是一個例子:

#include "Poco/Logger.h"
#include "Poco/SimpleFileChannel.h"
#include "Poco/AutoPtr.h"
using Poco::Logger;
using Poco::SimpleFileChannel;
using Poco::AutoPtr;
int main(int argc, char** argv)
{
          AutoPtr<SimpleFileChannel> pChannel(new SimpleFileChannel);
          pChannel->setProperty("path", "sample.log");
          pChannel->setProperty("rotation", "2 K");
          Logger::root().setChannel(pChannel);
          Logger& logger = Logger::get("TestLogger"); // inherits root channel
          for (int i = 0; i < 100; ++i)
          logger.information("Testing SimpleFileChannel");
          return 0;
}



1.4.5 文件通道

          Poco::FileChannel類提供了完整的日誌支持。每個消息的內容都會被添加到文件中,並使用一個新行輸出。Poco::FileChannel類支持按文件大小和時間間隔對日誌進行循環覆蓋,支持自動歸檔(使用不一樣的文件命名策略),支持壓縮(GZIP)和清除(根據已歸檔文件的日期或數量)歸檔日誌文件。

          文件通道屬性
         path:
 日誌文件的路徑
          rotation:日誌循環覆蓋模式。能夠有如下幾種選擇:
              never: 不須要循環覆蓋
              <n>: 若是超過 <n> 字節的話,循環覆蓋
              <n> K: 若是超過 <n> K字節的話,循環覆蓋
              <n> M: 若是超過 <n> M字節的話,循環覆蓋
              [day][hh:][mm]: 按照指定的日期和時間進行日誌的循環覆蓋
              daily/weekly/monthly: 按照日/周/月循環覆蓋
              <n> hours/weeks/months: 按照<n>小時/周/月進行循環覆蓋
          archive:歸檔日誌的目錄名
              number:從0開始自動增長的數字,被添加到日誌文件名後。最新的日誌文件數字老是0。
              timestamp: 時間戳以YYYYMMDDHHMMSS格式被添加到日誌文件名後
              times:指定循環的時間是按照本地時間仍是按照UTC時間。本地時間和utc時間都是能夠接受的合法時間。
              compress:自動壓縮存檔文件。指定true或者false。
             purgeAge:指定歸檔日誌的最大期限。當日志的生成時間超過此期限,將被刪除。格式爲 <n>[seconds]/minutes/hours/days/weeks/months
              purgeCount:指定歸檔日誌文件的最大數目。若是生成日誌的數目超過此最大數目,生成日期最先的文件將被刪除。

          下面是一個例子:

#include "Poco/Logger.h"
#include "Poco/FileChannel.h"
#include "Poco/AutoPtr.h"
using Poco::Logger;
using Poco::FileChannel;
using Poco::AutoPtr;
int main(int argc, char** argv)
{
          AutoPtr<FileChannel> pChannel(new FileChannel);
          pChannel->setProperty("path", "sample.log");
          pChannel->setProperty("rotation", "2 K");
          pChannel->setProperty("archive", "timestamp");
          Logger::root().setChannel(pChannel);
          Logger& logger = Logger::get("TestLogger"); // inherits root channel
          for (int i = 0; i < 100; ++i)
          logger.information("Testing FileChannel");
          return 0;
}



1.4.6 事件日誌通道(EventLogChannel)

          Poco::EventLogChannel僅被使用於操做系統Windows NT中,它將把日誌寫到"Windows事件日誌"中.Poco::EventLogChannel會把PocoFoundation.dll做爲消息定義資源註冊到"Windows事件日誌"中。當使用Window事件查看器來查看系統事件日誌時,事件查看器必需要找到PocoFoundation.dll,不然記錄的日誌消息將不可以被正常顯示。

          事件日誌通道屬性
              name
事件源的名字,一般是程序名。
              loghost, host:事件日誌服務在運行的主機的名稱。默認值爲本地主機
              logfile:日誌文件的名稱。默認是應用程序自己。


1.4.7 系統日誌通道(SyslogChannel)

          Poco::SyslogChannel僅適用於Unix平臺,會把日誌輸出到本地系統日誌守護程序。
          包含RemoteSyslogChannel類的網絡庫,能夠經過基於UDP的系統日誌協議(Syslog protoco)把日誌輸出到遠程的日誌守護程序上。


1.4.8 異步通道:

          Poco::AsyncChannel容許在另一個分離的線程中去記錄通道的日誌。這能夠把產生日誌的線程和記錄日誌的線程分開而實現解耦。全部的消息先被存儲在一個先進先出的消息隊列中,而後由一個單獨的線程從消息隊列中獲取,並最終把消息發送到輸出通道。

          下面是一個例子:

#include "Poco/Logger.h"
#include "Poco/AsyncChannel.h"
#include "Poco/ConsoleChannel.h"
#include "Poco/AutoPtr.h"
using Poco::Logger;
using Poco::AsyncChannel;
using Poco::ConsoleChannel;
using Poco::AutoPtr;
int main(int argc, char** argv)
{
          AutoPtr<ConsoleChannel> pCons(new ConsoleChannel);
          AutoPtr<AsyncChannel> pAsync(new AsyncChannel(pCons));
          Logger::root().setChannel(pAsync);
          Logger& logger = Logger::get("TestLogger");
          for (int i = 0; i < 10; ++i)
          logger.information("This is a test");
          return 0;
}

1.4.9 拆分通道(SplitterChannel)

          使用Poco::SplitterChannel能夠把消息發送給一個或者多個其餘的通道,即輸出日誌在多個目標中。使用下面的函數能夠在SplitterChannel中加入一個新通道:
               void addChannel(Channel* pChannel)

          下面是一個例子

#include "Poco/Logger.h"
#include "Poco/SplitterChannel.h"
#include "Poco/ConsoleChannel.h"
#include "Poco/SimpleFileChannel.h"
#include "Poco/AutoPtr.h"
using Poco::Logger;
using Poco::SplitterChannel;
using Poco::ConsoleChannel;
using Poco::SimpleFileChannel;
using Poco::AutoPtr;
int main(int argc, char** argv)
{
          AutoPtr<ConsoleChannel> pCons(new ConsoleChannel);
          AutoPtr<SimpleFileChannel> pFile(new SimpleFileChannel("test.log"));
          AutoPtr<SplitterChannel> pSplitter(new SplitterChannel);
          pSplitter->addChannel(pCons);
          pSplitter->addChannel(pFile);
          Logger::root().setChannel(pSplitter);
          Logger::root().information("This is a test");
          return 0;
}



1.5 LogStream類

         Poco::LogStream類提供了一個日誌的輸出流接口。能夠在日誌流中,格式化輸出日誌記錄消息。日誌消息必須以std::endl(或CR和LF字符)結尾。

         下面是 LogStream在日誌體系中的示意圖:


          消息的優先級能夠使用下列函數設定:

          LogStream& priority(Message::Priority prio)
          LogStream& fatal()
          LogStream& critical()
          LogStream& error()
          LogStream& warning()
          LogStream& notice()
          LogStream& information()
          LogStream& debug()
          LogStream& trace


          下面是一個例子:

#include "Poco/LogStream.h"
#include "Poco/Logger.h"
using Poco::Logger;
using Poco::LogStream;
int main(int argc, char** argv)
{
          Logger& logger = Logger::get("TestLogger");
          LogStream lstr(logger);
          lstr << "This is a test" << std::endl;
          return 0;
}



1.6  FormattingChannel類和Formatter類

          消息的格式

         


         FormattingChannel類和Formatter類負責格式化日誌消息。Poco::FormattingChannel會把它接受到的每個消息經過Poco::Formatter傳遞給下一個的輸出通道。                      Poco::Formatter是全部格式類的基類,同通道同樣,能夠被設置屬性。


1.6.1 PatternFormatter類

          Poco::PatternFormatter能夠根據打印格式去格式化消息。想要知道更多細節,能夠查看相關文檔。

          下面是一個例子:

#include "Poco/ConsoleChannel.h"
#include "Poco/FormattingChannel.h"
#include "Poco/PatternFormatter.h"
#include "Poco/Logger.h"
#include "Poco/AutoPtr.h"
using Poco::ConsoleChannel;
using Poco::FormattingChannel;
using Poco::PatternFormatter;
using Poco::Logger;
using Poco::AutoPtr;
int main(int argc, char** argv)
{
          AutoPtr<ConsoleChannel> pCons(new ConsoleChannel);
          AutoPtr<PatternFormatter> pPF(new PatternFormatter);
          pPF->setProperty("pattern", "%Y-%m-%d %H:%M:%S %s: %t");
          AutoPtr<FormattingChannel> pFC(new FormattingChannel(pPF, pCons));
          Logger::root().setChannel(pFC);
          Logger::get("TestChannel").information("This is a test");
          return 0;
}



1. 7 日誌效率的考慮:

          1. 建立消息可能要花費必定的時間(消息建立時須要獲取系統當前時間、進程ID和線程ID)
          2. 建立一個有意義的消息也須要時間,由於按輸出格式生成字符串是存在開銷的
          3. 消息一般狀況下是經過引用的方式傳遞給下一個通道。例外的狀況是,FormattingChannel和AsyncChannel類。它們會生成消息的一個副本。
          4. 對於每個日誌(logger)對象來講,一條消息要麼被輸出,要麼不被輸出,這由日誌和消息的級別共同決定。這個動做存在常數級別的開銷,僅是兩個int型的比較。
          5. 獲取日誌(logger)對象引用的操做開銷是基於對數的,這由std::map的查找特性所決定。在查找過程當中,日誌(logger)對象名稱的比較是線性的,這由std::string字符串比較特性所決定。
          6. 一般在一個程序中,獲取一個日誌(logger)對象引用(Logger::get())的操做,只會進行一次。
          7. 儘量的避免頻繁的調用Logger::get()函數,更好的方法是在經過函數得到日誌(logger)對象引用後,保存它。
          8. 記錄和輸出日誌的效率取決於日誌輸出的通道。通道的效率很是依賴於操做系統的實現。
          9. 構造消息(messages)的開銷包括了構造字符串,字符拼接,數字格式化等。
          10. 在構造消息前,推薦先查詢日誌器的等級,以決定是否須要構造消息。查詢等級能夠使用函數is(), fatal(), critical()等。
          11. 在Poco庫中提供了一些宏,用於在構造消息以前對日誌等級進行檢查。如poco_fatal(msg), poco_critical(msg), poco_error(msg)等。

          下面是一個例子:

// ...
if (logger.warning())
{
          std::string msg("This is a warning");
          logger.warning(msg);
}
 
// is equivalent to
poco_warning(logger, "This is a warning");
 
 

POCOC++庫學習和分析-- 日誌(二)

 

2. Poco日誌的實現

2.1 日誌模塊應該實現的業務

        在討論日誌的實現以前,先來聊一下日誌模塊應該實現那些業務。日誌的業務說簡單能夠很簡單,就是輸出記錄。說複雜也複雜,來看它的複雜性:
         首先,日誌的輸出對象是不一樣的,有控制檯輸出,本地文件輸出,網絡文件輸出,輸出到系統日誌等。假如是網絡日誌,日誌庫中其實還會包含網絡模塊,真是愈來愈複雜了。
         第二,日誌輸出的格式和內容。不一樣用戶關心的內容和喜歡的輸出格式是不一樣的,要知足全部人的需求,就必須可以提供全面的信息,並提供選項供用戶選擇。
         第三,日誌的級別。程序的日誌必定是須要動態可調的。程序日誌過多,消耗資源;日誌過少,沒法提供足夠的信息,用來定位和解決問題。
         第四,日誌的存儲策略。日誌是具備實效性的,日誌保存的時間越久,信息熵越低;日誌存儲也是須要成本的,大量的日誌會擠佔硬盤空間,因此須要對日誌的存儲進行管理。超過必定時間的日誌能夠考慮刪除。在磁盤資源緊張的狀況下,必須考慮控制日誌的大小。
         第五,日誌是用來查詢和排除問題的。爲了可以快速的定位問題,最好可以把日誌按照模塊輸出,這就要求日誌庫設計的時候考慮日誌模塊的分類。
         第六,這一點和日誌的業務無關,和庫的實現相關。跨平臺的話,必須考慮操做系統底層API的不一樣。

         對於日誌模塊的業務就討論到這裏,仍是回到Poco的日誌模塊上。首先來看一張Poco日誌模塊的類圖:




2.2. Message類

        下面是Message類的頭文件。其定義以下:

class Foundation_API Message
{
public:
            enum Priority
            {
                        PRIO_FATAL = 1,   /// A fatal error. The application will most likely terminate. This is the highest priority.
                        PRIO_CRITICAL,    /// A critical error. The application might not be able to continue running successfully.
                        PRIO_ERROR,       /// An error. An operation did not complete successfully, but the application as a whole is not affected.
                        PRIO_WARNING,     /// A warning. An operation completed with an unexpected result.
                        PRIO_NOTICE,      /// A notice, which is an information with just a higher priority.
                        PRIO_INFORMATION, /// An informational message, usually denoting the successful completion of an operation.
                        PRIO_DEBUG,       /// A debugging message.
                        PRIO_TRACE        /// A tracing message. This is the lowest priority.
            };
            
            Message();  
            Message(const std::string& source, const std::string& text, Priority prio);
            Message(const std::string& source, const std::string& text, Priority prio, const char* file, int line);       
            Message(const Message& msg);         
            Message(const Message& msg, const std::string& text);                    
            ~Message();
            Message& operator = (const Message& msg);        
 
            void swap(Message& msg);
            void setSource(const std::string& src);
            const std::string& getSource() const;
            void setText(const std::string& text);
            const std::string& getText() const;
            void setPriority(Priority prio);
            Priority getPriority() const;
            void setTime(const Timestamp& time); 
            const Timestamp& getTime() const;
            void setThread(const std::string& thread);
            const std::string& getThread() const;
            void setTid(long pid);
            long getTid() const;
            void setPid(long pid);  
            long getPid() const;
            void setSourceFile(const char* file);            
            const char* getSourceFile() const;
            void setSourceLine(int line);
            int getSourceLine() const;
            const std::string& operator [] (const std::string& param) const;
            std::string& operator [] (const std::string& param);
 
protected:
            void init();
            typedef std::map<std::string, std::string> StringMap;
 
private:    
            std::string _source;                 // 產生日誌的源
            std::string _text;                   // 日誌主內容
            Priority    _prio;                   // 日誌的優先級(某種程度上代表了日誌自己的信息含量)
            Timestamp   _time;                   // 日誌產生的時間
            int         _tid;                     // 日誌產生的線程
            std::string _thread;                 // 日誌產生的線程名
            long        _pid;                     // 日誌產生的進程名
            const char* _file;                   // 日誌產生的代碼文件
            int         _line;                    // 日誌產生的代碼文件行號
            StringMap*  _pMap;                   // 供用戶存儲其餘信息的map容器
};


         它的默認初始化函數爲:

Message::Message(): 
            _prio(PRIO_FATAL), 
            _tid(0), 
            _pid(0),
            _file(0),
            _line(0),
            _pMap(0) 
{
            init();
}
 
void Message::init()
{
#if !defined(POCO_VXWORKS)
            _pid = Process::id();
#endif
            Thread* pThread = Thread::current();
            if (pThread)
            {
                        _tid    = pThread->id();
                        _thread = pThread->name();
            }
}


         從上面的代碼能夠看出Message類提供了很是多的存儲選項,有日誌的源、線程信息、進程信息、優先級等。在此基礎上,爲了知足用戶的需求,還放了一個map來支持用戶定製。全部的信息,都在Message類構造的時候被賦值,真的挺強大。固然這一作法也會帶來一點程序上的開銷。



2.3 Configurable類

        在Poco庫裏,Configurable類是用來對日誌特性作配置的。其定義以下:

class Foundation_API Configurable
{
public:
            Configurable();                      
            virtual ~Configurable();
                        
            virtual void setProperty(const std::string& name, const std::string& value) = 0;      
            virtual std::string getProperty(const std::string& name) const = 0;
};


         從代碼看它自己是一個抽象類,提供了兩個接口,用來設置和獲取日誌屬性。看子類的代碼,可以知道,這兩個接口是用來完成字符解析工做的。



2.4 LogFile類

        LogFile是Poco日誌模塊的內部類,封裝了不一樣操做系統存檔文件記錄之間的差別,也就是說隱藏了操做系統之間對於文件輸入的區別。其定義以下:


#if defined(POCO_OS_FAMILY_WINDOWS) && defined(POCO_WIN32_UTF8)
#include "Poco/LogFile_WIN32U.h"
#elif defined(POCO_OS_FAMILY_WINDOWS)
#include "Poco/LogFile_WIN32.h"
#elif defined(POCO_OS_FAMILY_VMS)
#include "Poco/LogFile_VMS.h"
#else
#include "Poco/LogFile_STD.h"
#endif
 
namespace Poco {
class Foundation_API LogFile: public LogFileImpl
{
public:
            LogFile(const std::string& path);
            ~LogFile();
 
            void write(const std::string& text); 
            UInt64 size() const;    
            Timestamp creationDate() const;      
            const std::string& path() const;
};



2.5 策略類(Strategy)

        Strategy類也一樣是日誌系統內部的實現類,同時也是針對存檔文件操做設計的。對於存檔文件,Poco認爲存在3種策略,即:
         1. 對於文件存檔的策略
         2. 對於文件刪除的策略
         3. 對於文件覆蓋的策略

         對於文件存檔的策略由ArchiveStrategy類和其子類完成。它們完成的工做是對日誌文件的命名。ArchiveByNumberStrategy完成了日誌文件的數字命名,即程序產生的日誌會以log0、log一、...logn命名。ArchiveByTimestampStrategy完成了日誌文件的時間戳命名,即程序產生的日誌會以時間戳方式命名。
         在ArchiveStrategy類上還留有一個壓縮接口,用來設置存檔文件是否須要被壓縮。在Poco中,內置了gzip壓縮方式,這個具體由類ArchiveCompressor實現。關於這一點,咱們會在之後介紹。


         對於文件刪除的策略由PurgeStrategy類和其子類完成。PurgeByCountStrategy類,實現了按文件大小刪除的策略。而PurgeByAgeStrategy實現了按文件存儲時間刪除的
策略。來看一段PurgeByAgeStrategy::purge動做的代碼:

void PurgeByAgeStrategy::purge(const std::string& path)
{
            std::vector<File> files;
            list(path, files);
            for (std::vector<File>::iterator it = files.begin(); it != files.end(); ++it)
            {
                        if (it->getLastModified().isElapsed(_age.totalMicroseconds()))
                        {
                                     it->remove();
                        }
            }
}
 
void PurgeStrategy::list(const std::string& path, std::vector<File>& files)
{
            Path p(path);
            p.makeAbsolute();
            Path parent = p.parent();
            std::string baseName = p.getFileName();
            baseName.append(".");
 
            DirectoryIterator it(parent);
            DirectoryIterator end;
            while (it != end)
            {
                        if (it.name().compare(0, baseName.size(), baseName) == 0)
                        {
                                     files.push_back(*it);
                        }
                        ++it;
            }
}



         從代碼看PurgeByAgeStrategy::purge函數的輸入爲一個路徑。purge函數會遍歷這個目錄,查看文件信息,當文件歷史超過必定時間,則刪除。PurgeByCountStrategy與之相似。

         對於文件覆蓋的策略是由類RotateStrategy和其子類完成的。文件的覆蓋策略同刪除策略是不一樣的,覆蓋策略是一個循環策略。RotateAtTimeStrategy實現了按時間循環的功能。RotateByIntervalStrategy實現了按時間間隔循環的策略。RotateBySizeStrategy實現了按大小循環的策略。



2.6 格式類(Formatter)

        格式類是用來肯定輸出日誌最終內容的格式的。Message類提供了很是多的日誌信息,但並非全部信息都是用戶所感興趣的。Formatter被用來肯定最終消息輸出。在Poco庫中內置了一些格式輸出選項,由PatternFormatter完成。其定義以下:


class Foundation_API PatternFormatter: public Formatter
            /// This Formatter allows for custom formatting of
            /// log messages based on format patterns.
            ///
            /// The format pattern is used as a template to format the message and
            /// is copied character by character except for the following special characters,
            /// which are replaced by the corresponding value.
            ///
            ///   * %s - message source
            ///   * %t - message text
            ///   * %l - message priority level (1 .. 7)
            ///   * %p - message priority (Fatal, Critical, Error, Warning, Notice, Information, Debug, Trace)
            ///   * %q - abbreviated message priority (F, C, E, W, N, I, D, T)
            ///   * %P - message process identifier
            ///   * %T - message thread name
            ///   * %I - message thread identifier (numeric)
            ///   * %N - node or host name
            ///   * %U - message source file path (empty string if not set)
            ///   * %u - message source line number (0 if not set)
            ///   * %w - message date/time abbreviated weekday (Mon, Tue, ...)
            ///   * %W - message date/time full weekday (Monday, Tuesday, ...)
            ///   * %b - message date/time abbreviated month (Jan, Feb, ...)
            ///   * %B - message date/time full month (January, February, ...)
            ///   * %d - message date/time zero-padded day of month (01 .. 31)
            ///   * %e - message date/time day of month (1 .. 31)
            ///   * %f - message date/time space-padded day of month ( 1 .. 31)
            ///   * %m - message date/time zero-padded month (01 .. 12)
            ///   * %n - message date/time month (1 .. 12)
            ///   * %o - message date/time space-padded month ( 1 .. 12)
            ///   * %y - message date/time year without century (70)
            ///   * %Y - message date/time year with century (1970)
            ///   * %H - message date/time hour (00 .. 23)
            ///   * %h - message date/time hour (00 .. 12)
            ///   * %a - message date/time am/pm
            ///   * %A - message date/time AM/PM
            ///   * %M - message date/time minute (00 .. 59)
            ///   * %S - message date/time second (00 .. 59)
            ///   * %i - message date/time millisecond (000 .. 999)
            ///   * %c - message date/time centisecond (0 .. 9)
            ///   * %F - message date/time fractional seconds/microseconds (000000 - 999999)
            ///   * %z - time zone differential in ISO 8601 format (Z or +NN.NN)
            ///   * %Z - time zone differential in RFC format (GMT or +NNNN)
            ///   * %E - epoch time (UTC, seconds since midnight, January 1, 1970)
            ///   * %[name] - the value of the message parameter with the given name
            ///   * %% - percent sign
{
public:
            PatternFormatter();
                        /// Creates a PatternFormatter.
                        /// The format pattern must be specified with
                        /// a call to setProperty.
 
            PatternFormatter(const std::string& format);
                        /// Creates a PatternFormatter that uses the
                        /// given format pattern.
 
            ~PatternFormatter();
                        /// Destroys the PatternFormatter.
 
            void format(const Message& msg, std::string& text);
                        /// Formats the message according to the specified
                        /// format pattern and places the result in text. 
                        
            void setProperty(const std::string& name, const std::string& value);
                        /// Sets the property with the given name to the given value.
                        ///
                        /// The following properties are supported:
                        /// 
                        ///     * pattern: The format pattern. See the PatternFormatter class
                        ///       for details.
                        ///     * times: Specifies whether times are adjusted for local time
                        ///       or taken as they are in UTC. Supported values are "local" and "UTC".
                        ///
                        /// If any other property name is given, a PropertyNotSupported
                        /// exception is thrown. std::string getProperty(const std::string& name) const;
                        /// Returns the value of the property with the given name or
                        /// throws a PropertyNotSupported exception if the given
                        /// name is not recognized.
 
            static const std::string PROP_PATTERN;
            static const std::string PROP_TIMES;
 
protected:
            static const std::string& getPriorityName(int);          /// Returns a string for the given priority value.
            
private:
            bool        _localTime;
            std::string _pattern;
};

        固然若是用戶對已有的格式不滿意,能夠本身擴展。

 

2.7 Channel類

        Channel類能夠被當作爲全部輸出對象的抽象,它也是個抽像類。它繼承自Configurable和RefCountedObject。繼承自Configurable說明須要對配置信息進行必定的解析工做,繼承自RefCountedObject說明其自己是個引用計數對象,會使用AutoPtr去管理。
         其具體定義以下:

class Foundation_API Channel: public Configurable, public RefCountedObject
{
public:
            Channel();
            virtual void open();
                        
            virtual void close();                
            virtual void log(const Message& msg) = 0;                   
            void setProperty(const std::string& name, const std::string& value);
            std::string getProperty(const std::string& name) const;
                        
protected:
            virtual ~Channel();
            
private:
            Channel(const Channel&);
            Channel& operator = (const Channel&);
};


         Poco內部實現了很是多的Channel子類,被用於向不一樣的目標輸出日誌信息。不少Channel是依賴於平臺的,如EventLogChannel、SyslogChannel、OpcomChannel、WindowsConsoleChannel。它們都實現單一功能即向一個特殊的目標輸出。
         在Channel的子類中,比較特殊的有如下幾個:

         AsyncChannel:
         AsyncChannel類是個主動對象,在內部包含一個Thread對象,經過內部NotificationQueue隊列,完成了日誌生成和輸出的解耦。

         SplitterChannel:
         SplitterChannel類完成了一份消息,多份輸出的工做。它自己是一個Channel類的容器。其定義以下:

class Foundation_API SplitterChannel: public Channel
            /// This channel sends a message to multiple
            /// channels simultaneously.
{
public:
            SplitterChannel();                     
                        /// Creates the SplitterChannel.
 
            void addChannel(Channel* pChannel);
                        /// Attaches a channel, which may not be null.
                        
       &
相關文章
相關標籤/搜索