POCO C++库学习和分析 -- 序
阅读原文时间:2021年04月24日阅读:1

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

1. POCO库概述:

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

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

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

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

1.      跨平台库的封装

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

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

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

5.      泛型

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

2. Foundation 库分析:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3. 附录:

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

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

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

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

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

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

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

1)Core

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

2)Cache

顾名思义,内存Cache

3)Crypt

数字摘要

4)DateTime

时间

5)Events

分装了事件

6)Filesystem

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

7)Hashing

Hash表

8)Logging

日志系统

9)Notifications

通知

10)Processes

进程通讯

11)RegularExpression

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

12)SharedLibrary

文件和类的动态实时加载

13)Streams

14)Tasks

任务

15)Text

文本装换

16)Threading

多线程

17)URI

URI操作

18)UUID

UUID生成和操作

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

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

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

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

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

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

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

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

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

1.1 导出函数

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

        对于库提供方而言:

              //TestLibrary.cpp

#include

#if defined(_WIN32)

#define LIBRARY_API__declspec(dllexport)

#else

#define LIBRARY_API

#endif

extern "C" voidLIBRARY_API hello();

void hello()

{

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

}

// 对于使用方而言:

              //LibraryLoaderTest.cpp

#include"Poco/SharedLibrary.h"

using Poco::SharedLibrary;

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

int main(int argc, char** argv)

{

        std::stringpath("TestLibrary");

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

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

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

        func();

        library.unload();

        return 0;

}

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

1.2  导出类

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

.h文件

// AbstractPlugin.h

//

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

#ifndefAbstractPlugin_INCLUDED

#defineAbstractPlugin_INCLUDED

class AbstractPlugin

{

public:

                     AbstractPlugin();

                     virtual ~AbstractPlugin();

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

};

#endif // AbstractPlugin.h

.cpp文件

         //AbstractPlugin.cpp

//

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

#include "AbstractPlugin.h"

AbstractPlugin::AbstractPlugin()

{}

AbstractPlugin::~AbstractPlugin()

{}

// PluginLibrary.cpp

#include "AbstractPlugin.h"

#include "Poco/ClassLibrary.h"

#include

class PluginA: public AbstractPlugin

{

public:

                   std::stringname() const

                   {

                            return"PluginA";

                   }

};

class PluginB: public AbstractPlugin

{

public:

                   std::stringname() const

                   {

                            return"PluginB";

                   }

};

POCO_BEGIN_MANIFEST(AbstractPlugin)

POCO_EXPORT_CLASS(PluginA)

POCO_EXPORT_CLASS(PluginB)

POCO_END_MANIFEST

// optional set up and clean up functions

void pocoInitializeLibrary()

{

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

}

void pocoUninitializeLibrary()

{

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

}

          对于使用方来说:

                   //main.cpp

#include "Poco/ClassLoader.h"

#include "Poco/Manifest.h"

#include "AbstractPlugin.h"

#include

typedef Poco::ClassLoaderPluginLoader;

typedef Poco::ManifestPluginManifest;

int main(int argc, char** argv)

{

                   PluginLoaderloader;

                   std::stringlibName("PluginLibrary");

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

                   loader.loadLibrary(libName);

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

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

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

                   {

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

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

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

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

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

                   }

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

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

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

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

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

                   deletepPluginB;

                   loader.unloadLibrary(libName);

                   return0;

}

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

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

1.  数据保护-锁

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

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

Mutex

Semaphore

Event

ReadWriteLock

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

2.  线程

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

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

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

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

{

#ifdefined(_DLL)

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

#else

unsigned threadId;

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

_threadId =static_cast(threadId);

#endif

if (!_thread)

throw SystemException("cannotcreate thread");

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

throw SystemException("cannotset thread priority");

}

#ifdefined(_DLL)

DWORDWINAPI ThreadImpl::callableEntry(LPVOID pThread)

#else

unsigned__stdcall ThreadImpl::callableEntry(void* pThread)

#endif

{

_currentThreadHolder.set(reinterpret_cast(pThread));

#ifdefined(_DEBUG) && defined(POCO_WIN32_DEBUGGER_THREAD_NAMES)

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

#endif

try

{

ThreadImpl* pTI =reinterpret_cast(pThread);

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

}

catch (Exception& exc)

{

ErrorHandler::handle(exc);

}

catch (std::exception& exc)

{

ErrorHandler::handle(exc);

}

catch (…)

{

ErrorHandler::handle();

}

return 0;

}

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

[cpp] view plaincopy

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

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

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

[cpp] view plaincopy

  1. ThreadImpl* pTI = reinterpret_cast(pThread);  

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

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

  1. #include "Poco/Thread.h"  

  2. #include "Poco/Runnable.h"  

  3. #include   

  4. class HelloRunnable: public Poco::Runnable  

  5. {  

  6.        virtual void run()  

  7.        {  

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

  9.        }  

  10. };  

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

  12. {  

  13.        HelloRunnable runnable;  

  14.        Poco::Thread thread;  

  15.        thread.start(runnable);  

  16.        thread.join();  

  17.        return 0;  

  18. }  

3.  线程池

3.1线程池的基本概念

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

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

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

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

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

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

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

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

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

线程池需要具备的元素

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

3.2 Poco中线程池实现

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

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

class ThreadPool

{

public:

              ThreadPool(intminCapacity = 2,

                            intmaxCapacity = 16,

                            intidleTime = 60,

                            intstackSize = POCO_THREAD_STACK_SIZE);

              ThreadPool(conststd::string& name,

                            intminCapacity = 2,

                            intmaxCapacity = 16,

                            intidleTime = 60,

                            intstackSize = POCO_THREAD_STACK_SIZE);

              ~ThreadPool();

              voidaddCapacity(int n);

              intcapacity() const;

              voidsetStackSize(int stackSize);

              intgetStackSize() const;

              intused() const;

              intallocated() const;

              intavailable() const;

              voidstart(Runnable& target);

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

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

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

              voidstopAll();

              voidjoinAll();

              voidcollect();

              conststd::string& name() const;

              staticThreadPool& defaultPool();

protected:

              PooledThread*getThread();

              PooledThread*createThread();

              voidhousekeep();

private:

              ThreadPool(constThreadPool& pool);

              ThreadPool&operator = (const ThreadPool& pool);

              typedefstd::vector ThreadVec;

              std::string_name;

              int_minCapacity;

              int_maxCapacity;

              int_idleTime;

              int_serial;

              int_age;

              int_stackSize;

              ThreadVec_threads;

              mutableFastMutex _mutex;

};

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

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

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

         void ThreadPool::start(Runnable&target)

{

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

}

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

         PooledThread* ThreadPool::getThread()

{

         FastMutex::ScopedLock lock(_mutex);

         if (++_age == 32)

                   housekeep();

         PooledThread* pThread = 0;

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

         {

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

         }

         if (!pThread)

         {

                   if (_threads.size() <_maxCapacity)

                   {

            pThread = createThread();

            try

            {

                pThread->start();

                _threads.push_back(pThread);

            }

            catch (…)

            {

                delete pThread;

                throw;

            }

                   }

                   else throwNoThreadAvailableException();

         }

         pThread->activate();

         return pThread;

}

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

class PooledThread: publicRunnable

{

public:

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

              ~PooledThread();

              void start();

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

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

              bool idle();

              int idleTime();

              void join();

              void activate();

              void release();

              void run();

private:

              volatile bool       _idle;

              volatile std::time_t _idleTime;

              Runnable*           _pTarget;

              std::string         _name;

              Thread              _thread;

              Event               _targetReady;

              Event               _targetCompleted;

              Event               _started;

              FastMutex           _mutex;

};

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

void PooledThread::run()

{

         _started.set();

         for(;;)

         {

                   _targetReady.wait();

                   _mutex.lock();

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

                   {

                            _mutex.unlock();

                            try

                            {

                                     _pTarget->run();

                            }

                            catch(Exception& exc)

                            {

                                     ErrorHandler::handle(exc);

                            }

                            catch(std::exception& exc)

                            {

                                     ErrorHandler::handle(exc);

                            }

                            catch(…)

                            {

                                      ErrorHandler::handle();

                            }

                            FastMutex::ScopedLocklock(_mutex);

                            _pTarget  = 0;

#if defined(_WIN32_WCE)

                            _idleTime= wceex_time(NULL);

#else

                            _idleTime= time(NULL);

#endif

                            _idle     = true;

                            _targetCompleted.set();

                            ThreadLocalStorage::clear();

                            _thread.setName(_name);

                            _thread.setPriority(Thread::PRIO_NORMAL);

                   }

                   else

                   {

                            _mutex.unlock();

                            break;

                   }

         }

}

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

3.3 其他

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

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

4. 定时器

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

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

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

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

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

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

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

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

                            /// startInterval expires.

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

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

4.1 定时器实现

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

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

void Timer::run()

{

              Poco::Timestamp now;

              long interval(0);

              do

              {

                            long sleep(0);

                            do

                            {

                                          now.update();

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

                                          if (sleep < 0)

                                          {

                                                        if (interval== 0)

                                                        {

                                                                      sleep= 0;

                                                                      break;

                                                        }

                                                        _nextInvocation+= interval*1000;

                                                        ++_skipped;

                                          }

                            }

                            while (sleep < 0);

                            if(_wakeUp.tryWait(sleep))

                            {

                                          Poco::FastMutex::ScopedLocklock(_mutex);

                                          _nextInvocation.update();

                                          interval =_periodicInterval;

                            }

                            else

                            {

                                          try

                                          {

                                                        _pCallback->invoke(*this);

                                          }

                                          catch (Poco::Exception&exc)

                                          {

                                                        Poco::ErrorHandler::handle(exc);

                                          }

                                          catch (std::exception&exc)

                                          {

                                                        Poco::ErrorHandler::handle(exc);

                                          }

                                          catch (…)

                                          {

                                                        Poco::ErrorHandler::handle();

                                          }

                                          interval =_periodicInterval;

                            }

                            _nextInvocation += interval*1000;

                            _skipped = 0;

              }

              while (interval > 0);

              _done.set();

}

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

4.2 定时器使用

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

#include"Poco/Timer.h"

#include"Poco/Thread.h"

using Poco::Timer;

using Poco::TimerCallback;

class TimerExample

{

              public:

              void onTimer(Poco::Timer& timer)

              {

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

              }

};

int main(int argc, char**argv)

{

              TimerExample te;

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

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

              Thread::sleep(5000);

              timer.stop();

              return 0;

}

5. 主动对象

5.1 线程回顾

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

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

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

staticThreadPool& defaultPool();

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

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

5.2 主动对象

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

         classMyObject : public Runnable

{

public:

     voidstart();

     voidstop();

     voidrun();

public:

     voidrealrun();

public:

     Thread_thread;

};

void MyObject::start()

{

     _thread.start();

}

void MyObject::stop()

{

     _thread.join();

}

void MyObject::run()

{

     this->realrun();

}

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

5.3 Activities

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

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

Activity(C*pOwner, Callback method):

     _pOwner(pOwner),

     _runnable(*pOwner, method),

     _stopped(true),

     _running(false),

     _done(false)

/// Creates theactivity. Call start() to

/// start it.

{

     poco_check_ptr (pOwner);

}

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

#include"Poco/Activity.h"

#include"Poco/Thread.h"

#include

usingPoco::Thread;

classActivityExample

{

public:

      ActivityExample(): _activity(this,

         &ActivityExample::runActivity)

      {}

      void start()

      {

           _activity.start();

      }

      void stop()

      {

           _activity.stop(); // request stop

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

      }

protected:

      void runActivity()

     {

          while (!_activity.isStopped())

         {

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

              Thread::sleep(200);

         }

      }

private:

      Poco::Activity _activity;

};

int main(intargc, char** argv)

{

      ActivityExample example;

      example.start();

      Thread::sleep(2000);

      example.stop();

      return 0;

}

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

5.4 Active Methods

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

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

template >

classActiveMethod

{

// …

}

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

template

class ActiveRunnable: publicActiveRunnableBase

{

// …

}

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

#include"Poco/ActiveMethod.h"

#include"Poco/ActiveResult.h"

#include

using Poco::ActiveMethod;

using Poco::ActiveResult;

class ActiveAdder

{

public:

     ActiveAdder():

       add(this, &ActiveAdder::addImpl)

     {}

     ActiveMethod, ActiveAdder> add;

private:

     int addImpl(const std::pair& args)

     {

          return args.first + args.second;

     }

};

int main(int argc, char** argv)

{

     ActiveAdder adder;

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

     sum.wait();

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

     return 0;

}

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

       不同的地方是:

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

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

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

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

ActiveMethod, ActiveAdder>

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

ActiveResultTypeoperator () (const ArgType& arg)

///Invokes the ActiveMethod.

{

      ActiveResultType result(newActiveResultHolder());

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

      StarterType::start(_pOwner, pRunnable);

      return result;

}

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

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

{

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

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

}

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

voidrun()

{

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

    try

    {

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

    }

    catch (Exception& e)

    {

       _result.error(e);

    }

    catch (std::exception& e)

    {

      _result.error(e.what());

    }

    catch (…)

   {

     _result.error("unknownexception");

   }

   _result.notify();

}

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

voidActiveResult::wait()

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

{

   _pHolder->wait();

}

 于是整个流程呼之欲出。

  ActiveMethods的类图如下:

5.5 ActiveDispatcher

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

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

#include "Poco/ActiveMethod.h"

#include "Poco/ActiveResult.h"

#include "Poco/ActiveDispatcher.h"

#include

using Poco::ActiveMethod;

using Poco::ActiveResult;

class ActiveAdder: public Poco::ActiveDispatcher

{

public:

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

      {}

     ActiveMethod, ActiveAdder,

     Poco::ActiveStarter > add;

private:

     int addImpl(conststd::pair& args)

     {

         returnargs.first + args.second;

     }

};

int main(int argc, char** argv)

{

     ActiveAdder adder;

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

     sum.wait();

     std::cout <<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

                   (pm,&ProgressHandler::onProgress)

                   );

         tm.addObserver(

                   Observer

                   (pm,&ProgressHandler::onFinished)

                   );

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

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

         tm.joinAll();

         return0;

}

3. Task类图

        最后给出Poco中Task的类图。

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

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

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

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

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

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

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

1.1 引用计数(Reference Counting)

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

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

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

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

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

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

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

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

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

1.2 对象所有权(Object Ownership)

 对象所有权有如下特点:

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

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

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

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

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

        二者关系如下:

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

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

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

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

2 AutoPtr

2.1 Poco中AutoPtr的例子

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

#include"Poco/AutoPtr.h"

#include"Poco/RefCountedObject.h"

class A: publicPoco::RefCountedObject

{

};

int main(int argc, char**argv)

{

     Poco::AutoPtr p1(new A);

     A* pA = p1;

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

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

     Poco::AutoPtr p3;

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

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

     return 0;

}

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

class Foundation_APIRefCountedObject

              /// A base class for objects that employ

              /// reference counting based garbage collection.

              ///

              /// Reference-counted objects inhibit construction

              /// by copying and assignment.

{

public:

              RefCountedObject();

                            /// Creates the RefCountedObject.

                            /// The initial reference count is one.

              void duplicate() const;

                            /// Increments the object's referencecount.

              void release() const;

                            /// Decrements the object's referencecount

                            /// and deletes the object if the count

                            /// reaches zero.

              int referenceCount() const;

                            /// Returns the reference count.

protected:

              virtual ~RefCountedObject();

                            /// Destroys the RefCountedObject.

private:

              RefCountedObject(const RefCountedObject&);

              RefCountedObject& operator = (constRefCountedObject&);

              mutable AtomicCounter _counter;

};

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

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

#include "Poco/AutoPtr.h"

using Poco::AutoPtr;

class RCO

{

public:

      RCO(): _rc(1)

      {

      }

      void duplicate()

      {

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

      }

      void release()

      {

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

      }

private:

      int _rc;

};

int main(int argc, char** argv)

{

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

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

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

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

      p2 = 0;                                                // _rc == 2

      p3 = 0;                                                // _rc == 1

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

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

      // pRCO and pNew now invalid!

      p1 = new RCO;                                           //_rc == 1

      return 0;

}

                                                              // _rc == 0 -> deleted

2.2 Poco中AutoPtr的类图

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

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

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

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

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

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

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

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

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

AutoPtr& assign(C* ptr)

{

              if(_ptr != ptr)

              {

                            if(_ptr) _ptr->release();

                            _ptr= ptr;

              }

              return*this;

}

AutoPtr& operator = (C* ptr)

{

              returnassign(ptr);

}

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

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

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

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

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

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

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

      
        Poco::AutoPtr与转换函数:

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

template

AutoPtr cast()const

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

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

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

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

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

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

{

              Other* pOther = dynamic_cast(_ptr);

              return AutoPtr(pOther, true);

}

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

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

template

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

{

       if (_ptr) _ptr->duplicate();

}

template

AutoPtr& assign(constAutoPtr& ptr)

{

       if (ptr.get() != _ptr)

       {

                     if (_ptr) _ptr->release();

                     _ptr = const_cast(ptr.get());

                     if (_ptr) _ptr->duplicate();

       }

       return *this;

}

template

AutoPtr& operator =(const AutoPtr& ptr)

{

       return assign(ptr);

}

注意事项和陷阱:

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

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

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

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

#include"Poco/AutoPtr.h"

#include "Poco/RefCountedObject.h"

class A: publicPoco::RefCountedObject

{

};

int main(int argc, char**argv)

{

         Poco::AutoPtr p1(new A);

         A* pA = p1;

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

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

         Poco::AutoPtr p3;

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

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

         return 0;

}

2.4 其他

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

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

3. SharedPtr

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

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

3.1 SharedPtr的类图

        首先来看一下SharedPtr的类图:

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

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

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

         template >

class SharedPtr

{

         // …

}

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

3.2 SharedPtr操作符和值语义

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

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

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

SharedPtr& operator = (C* ptr)

{

         returnassign(ptr);

}

SharedPtr& assign(C* ptr)

{

         if(get() != ptr)

         {

                   RC*pTmp = new RC;

                   release();

                   _pCounter= pTmp;

                   _ptr= ptr;

         }

         return*this;

}

void release()

{

         poco_assert_dbg(_pCounter);

         inti = _pCounter->release();

         if(i == 0)

         {

                   RP::release(_ptr);

                   _ptr= 0;

                   delete_pCounter;

                   _pCounter= 0;

         }

}

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

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

3.3 SharedPtr和Cast类型转换

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

template

SharedPtr cast() const

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

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

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

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

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

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

{

         Other*pOther = dynamic_cast(_ptr);

         if(pOther)

                   returnSharedPtr(_pCounter, pOther);

         returnSharedPtr();

}

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

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

template

SharedPtr& operator = (constSharedPtr& ptr)

{

         returnassign(ptr);

}

template

SharedPtr& assign(const SharedPtr& ptr)

{

         if(ptr.get() != _ptr)

         {

                   SharedPtrtmp(ptr);

                   swap(tmp);

         }

         return*this;

}

template

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

{

         _pCounter->duplicate();

}

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

#include "Poco/SharedPtr.h"

class A

{

public:

         virtual~A()

         {}

};

class B: public A

{

};

class C: public A

{

};

int main(int argc, char** argv)

{

         Poco::SharedPtrpA;

         Poco::SharedPtrpB(new B);

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

         pA= new B;

         //pB = pA;                     // will notcompile

         pB= pA.cast();              //okay

         Poco::SharedPtrpC(new C);

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

         return0;

}

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

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

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

      下面是SharedPtr的一个例子:

#include"Poco/SharedPtr.h"

#include

#include

using Poco::SharedPtr;

int main(int argc, char**argv)

{

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

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

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

              p2 = 0;                                                   // rc == 1

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

              p2 = p1;                                                  // rc == 2

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

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

              return 0;

}

// rc == 0 -> deleted

3.5 SharedPtr和数组

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

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

3.6 其他

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

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

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

4. AutoReleasePool

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

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

#include"Poco/AutoReleasePool.h"

using Poco::AutoReleasePool;

class C

{

public:

              C()

              {}

              void release()

              {

                            delete this;

              }

};

int main(int argc, char**argv)

{

              AutoReleasePool pool;

              C* pC = new C;

              pool.add(pC);

              pC = new C;

              pool.add(pC);

              return 0;

}

// all C's deleted

其类图如下:

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

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

                   AutoReleasePoolarp;

AutoPtr ptr = new C;

 …

arp.add(ptr.duplicate());

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

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

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

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

template

class DynamicFactory

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

{

          ….

          std::map FactoryMap _map;

}

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

template

class DynamicFactory

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

{

public:

              typedef AbstractInstantiatorAbstractFactory;

         ….

}

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

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

void registerClass(conststd::string& className)

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

              /// The DynamicFactory takes ownership of theinstantiator and deletes

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

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

              /// and the instantiator is deleted.

{

              registerClass(className, new Instantiator);

}

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

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

              /// The DynamicFactory takes ownership of theinstantiator and deletes

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

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

              /// and the instantiator is deleted.

{

              poco_check_ptr (pAbstractFactory);

              FastMutex::ScopedLock lock(_mutex);

              std::auto_ptrptr(pAbstractFactory);

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

              if (it == _map.end())

                            _map[className] = ptr.release();

              else

                            throw ExistsException(className);

}

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

template

class DynamicFactory

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

{

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

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

                            /// The class must have been registeredwith registerClass.

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

              {

                            FastMutex::ScopedLock lock(_mutex);

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

                            if (it != _map.end())

                                          returnit->second->createInstance();

                            else

                                          throwNotFoundException(className);

              }

}

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

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

#include"Poco/DynamicFactory.h"

#include"Poco/SharedPtr.h"

using Poco::DynamicFactory;

using Poco::SharedPtr;

class Base

{

};

class A: public Base

{

};

class B: public Base

{

};

int main(int argc, char**argv)

{

              DynamicFactory factory;

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

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

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

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

              // you can unregister classes

              factory.unregisterClass("B");

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

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

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

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

              return 0;

}

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

#include"Poco/DynamicFactory.h"

using Poco::DynamicFactory;

usingPoco::AbstractInstantiator;

class Base

{

};

class A: public Base

{

};

class C: public Base

{

public:

              C(int i): _i(i)

              {}

private:

              int _i;

};

class CInstantiator: publicAbstractInstantiator

{

public:

              CInstantiator(int i): _i(i)

              {}

              Base* createInstance() const

              {

                            return new C(_i);

              }

private:

              int _i;

};

int main(int argc, char**argv)

{

              DynamicFactory factory;

              factory.registerClass("A");

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

              return 0;

}

  最后给出 AbstractFactory模块的类图:

6. 内存池(Memory Pools)

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

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

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

       2. 解决内存碎片问题      

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

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

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

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

       其头文件中的定义如下:

class Foundation_APIMemoryPool

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

              ///

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

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

              /// fragmentation in situations where the same blocks

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

              /// applications.

              ///

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

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

              /// Blocks can be preallocated.

{

public:

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

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

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

              ~MemoryPool();

              void* get();

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

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

                            ///

                            /// If maxAlloc blocks are alreadyallocated, an

                            /// OutOfMemoryException is thrown.

              void release(void* ptr);

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

              std::size_t blockSize() const;

                            /// Returns the block size.

              int allocated() const;

                            /// Returns the number of allocatedblocks.

              int available() const;

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

private:

              MemoryPool();

              MemoryPool(const MemoryPool&);

              MemoryPool& operator = (const MemoryPool&);

              enum

              {

                            BLOCK_RESERVE = 128

              };

              typedef std::vector BlockVec;

              std::size_t _blockSize;

              int        _maxAlloc;

              int        _allocated;

              BlockVec    _blocks;

              FastMutex  _mutex;

};

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

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

#include"Poco/MemoryPool.h"

#include

#include

using Poco::MemoryPool;

int main(int argc, char**argv)

{

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

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

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

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

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

              std::string s(buffer, n);

              pool.release(buffer);

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

              return 0;

}

最后给出MemoryPool的类图:

7. 单件(Singletons)

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

template

class SingletonHolder

              /// This is a helper template class for managing

              /// singleton objects allocated on the heap.

              /// The class ensures proper deletion (including

              /// calling of the destructor) of singleton objects

              /// when the application that created them terminates.

{

public:

              SingletonHolder():

                            _pS(0)

                            /// Creates the SingletonHolder.

              {

              }

              ~SingletonHolder()

                            /// Destroys the SingletonHolder and thesingleton

                            /// object that it holds.

              {

                            delete _pS;

              }

              S* get()

                            /// Returns a pointer to the singletonobject

                            /// hold by the SingletonHolder. Thefirst call

                            /// to get will create the singleton.

              {

                            FastMutex::ScopedLock lock(_m);

                            if (!_pS) _pS = new S;

                            return _pS;

              }

private:

              S* _pS;

              FastMutex _m;

};

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

      其类图如下:

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

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

1. 进程

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

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

     1. 创建新进程

     2. 销毁其他进程

     3. 获取当前进程信息

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

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

2.  进程间同步

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

       其类图如下:

3. 管道

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

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

#include"Poco/Process.h"

#include"Poco/PipeStream.h"

#include"Poco/StreamCopier.h"

#include

using Poco::Process;

using Poco::ProcessHandle;

int main(int argc, char** argv)

{

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

              std::vector args;

              args.push_back("-ax");

              Poco::Pipe outPipe;

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

              Poco::PipeInputStream istr(outPipe);

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

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

              return 0;

}

管道的类图如下:

4. 共享内存

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

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

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

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

char* begin() const;

char* end() const;

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

例子一:

// Map a file into memory

#include"Poco/SharedMemory.h"

#include"Poco/File.h"

using Poco::SharedMemory;

using Poco::File;

int main(int argc, char**argv)

{

              File f("MapIntoMemory.dat");

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

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

              {

                            // …

              }

              return 0;

}

例子二:

// Share a memory region of1024 bytes

#include"Poco/SharedMemory.h"

using Poco::SharedMemory;

int main(int argc, char**argv)

{

              SharedMemory mem("MySharedMemory", 1024,

                            SharedMemory::AM_READ |SharedMemory::AM_WRITE);

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

              {

                            *ptr = 0;

              }

              return 0;

}

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

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

1. 信息交流的方法

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

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

1.1 放置模型于编程语言

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

[cpp] view plaincopy

  1. Source.Register(Target)  

  2. {  

  3.      source.vec.add(Target);  

  4. }  

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

[cpp] view plaincopy

  1. Soucre.Send(Msg)  

  2. {  

  3.     Target.Receive(Msg);  

  4. }  

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

[cpp] view plaincopy

  1. Target.Receive(Msg)  

  2. {  

  3.     switch(Msg)  

  4.     {  

  5.        case Msg1:  

  6.          doing something;  

  7.        case Msg2:  

  8.          doing something;  

  9.        default:  

  10.          doing something;  

  11.     }  

  12. }  

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

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

[cpp] view plaincopy

  1. Msg.Register(Target)  

  2.   

  3.     Msg.vec.add(Target);  

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

[cpp] view plaincopy

  1. Msg.Send(Source)  

  2. {  

  3.     switch(Source)  

  4.     {  

  5.        case Source1:  

  6.          doing something;  

  7.        case Source2:  

  8.          doing something;  

  9.        default:  

  10.          doing something;  

  11.     }  

  12. }  

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

1.2 放置模型于多线程环境

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

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

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

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

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

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

2. 通知和事件的总览

2.1 相关类信息

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

2.2 概述

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

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

3. 同步通知

3.1 消息

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

[cpp] view plaincopy

  1. class Notification: public RefCountedObject  

  2. {  

  3. public:  

  4.     typedef AutoPtr Ptr;  

  5.     Notification();  

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

  7.   

  8.   

  9. protected:  

  10.     virtual ~Notification();  

  11. };  

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

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

3.2 消息的发送者 source

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

[cpp] view plaincopy

  1. class NotificationCenter  

  2. {  

  3. public:  

  4.     NotificationCenter();  

  5.     ~NotificationCenter();  

  6.   

  7.     void addObserver(const AbstractObserver& observer);  

  8.     void removeObserver(const AbstractObserver& observer);  

  9.   

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

  11.   

  12.     bool hasObservers() const;    

  13.     std::size_t countObservers() const;  

  14.           

  15.     static NotificationCenter& defaultCenter();  

  16.   

  17. private:  

  18.     typedef SharedPtr AbstractObserverPtr;  

  19.     typedef std::vector ObserverList;  

  20.   

  21.     ObserverList  _observers;  

  22.     mutable Mutex _mutex;  

  23. };  

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

[cpp] view plaincopy

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

  2. {  

  3.     poco_check_ptr (pNotification);  

  4.   

  5.   

  6.     ScopedLockWithUnlock lock(_mutex);  

  7.     ObserverList observersToNotify(_observers);  

  8.     lock.unlock();  

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

  10.   

  11.   

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

  13.     {  

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

  15.     }  

  16. }  

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

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

3.3 消息的接收者 target

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

[cpp] view plaincopy

  1. class AbstractObserver  

  2. {  

  3. public:  

  4.     AbstractObserver();  

  5.     AbstractObserver(const AbstractObserver& observer);  

  6.     virtual ~AbstractObserver();  

  7.       

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

  9.   

  10.   

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

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

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

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

  15.     virtual void disable() = 0;  

  16. };  

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

[cpp] view plaincopy

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

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

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

[cpp] view plaincopy

  1. template <class C, class N>  

  2. class Observer: public AbstractObserver  

  3. {  

  4. public:  

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

  6.   

  7.   

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

  9.         _pObject(&object),   

  10.         _method(method)  

  11.     {  

  12.     }  

  13.       

  14.     Observer(const Observer& observer):  

  15.         AbstractObserver(observer),  

  16.         _pObject(observer._pObject),   

  17.         _method(observer._method)  

  18.     {  

  19.     }  

  20.       

  21.     ~Observer()  

  22.     {  

  23.     }  

  24.       

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

  26.     {  

  27.         if (&observer != this)  

  28.         {  

  29.             _pObject = observer._pObject;  

  30.             _method  = observer._method;  

  31.         }  

  32.         return *this;  

  33.     }  

  34.       

  35.     void notify(Notification* pNf) const  

  36.     {  

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

  38.   

  39.   

  40.         if (_pObject)  

  41.         {  

  42.             N* pCastNf = dynamic_cast(pNf);  

  43.             if (pCastNf)  

  44.             {  

  45.                 pCastNf->duplicate();  

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

  47.             }  

  48.         }  

  49.     }  

  50.       

  51.     bool equals(const AbstractObserver& abstractObserver) const  

  52.     {  

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

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

  55.     }  

  56.   

  57.   

  58.     bool accepts(Notification* pNf) const  

  59.     {  

  60.         return dynamic_cast(pNf) != 0;  

  61.     }  

  62.       

  63.     AbstractObserver* clone() const  

  64.     {  

  65.         return new Observer(*this);  

  66.     }  

  67.       

  68.     void disable()  

  69.     {  

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

  71.           

  72.         _pObject = 0;  

  73.     }  

  74.       

  75. private:  

  76.     Observer();  

  77.   

  78.   

  79.     C*       _pObject;  

  80.     Callback _method;  

  81.     mutable Poco::Mutex _mutex;  

  82. };  

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

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

3.4 一个使用例子

[cpp] view plaincopy

  1. #include "Poco/NotificationCenter.h"  

  2. #include "Poco/Notification.h"  

  3. #include "Poco/Observer.h"  

  4. #include "Poco/NObserver.h"  

  5. #include "Poco/AutoPtr.h"  

  6. #include   

  7. using Poco::NotificationCenter;  

  8. using Poco::Notification;  

  9. using Poco::Observer;  

  10. using Poco::NObserver;  

  11. using Poco::AutoPtr;  

  12. class BaseNotification: public Notification  

  13. {  

  14. };  

  15. class SubNotification: public BaseNotification  

  16. {  

  17. };  

  18.   

  19.   

  20. class Target  

  21. {  

  22. public:  

  23.     void handleBase(BaseNotification* pNf)  

  24.     {  

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

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

  27.     }  

  28.     void handleSub(const AutoPtr& pNf)  

  29.     {  

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

  31.     }  

  32. };  

  33.   

  34.   

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

  36. {  

  37.     NotificationCenter nc;  

  38.     Target target;  

  39.     nc.addObserver(  

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

  41.         );  

  42.     nc.addObserver(  

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

  44.         );  

  45.     nc.postNotification(new BaseNotification);  

  46.     nc.postNotification(new SubNotification);  

  47.     nc.removeObserver(  

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

  49.         );  

  50.     nc.removeObserver(  

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

  52.         );  

  53.     return 0;  

  54. }  

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

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

2. 通知和事件的总览

2.1 相关类信息

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

2.2 概述

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

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

3. 同步通知

3.1 消息

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

[cpp] view plaincopy

  1. class Notification: public RefCountedObject  

  2. {  

  3. public:  

  4.     typedef AutoPtr Ptr;  

  5.     Notification();  

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

  7.   

  8.   

  9. protected:  

  10.     virtual ~Notification();  

  11. };  

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

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

3.2 消息的发送者 source

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

[cpp] view plaincopy

  1. class NotificationCenter  

  2. {  

  3. public:  

  4.     NotificationCenter();  

  5.     ~NotificationCenter();  

  6.   

  7.     void addObserver(const AbstractObserver& observer);  

  8.     void removeObserver(const AbstractObserver& observer);  

  9.   

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

  11.   

  12.     bool hasObservers() const;    

  13.     std::size_t countObservers() const;  

  14.           

  15.     static NotificationCenter& defaultCenter();  

  16.   

  17. private:  

  18.     typedef SharedPtr AbstractObserverPtr;  

  19.     typedef std::vector ObserverList;  

  20.   

  21.     ObserverList  _observers;  

  22.     mutable Mutex _mutex;  

  23. };  

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

[cpp] view plaincopy

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

  2. {  

  3.     poco_check_ptr (pNotification);  

  4.   

  5.   

  6.     ScopedLockWithUnlock lock(_mutex);  

  7.     ObserverList observersToNotify(_observers);  

  8.     lock.unlock();  

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

  10.   

  11.   

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

  13.     {  

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

  15.     }  

  16. }  

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

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

3.3 消息的接收者 target

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

[cpp] view plaincopy

  1. class AbstractObserver  

  2. {  

  3. public:  

  4.     AbstractObserver();  

  5.     AbstractObserver(const AbstractObserver& observer);  

  6.     virtual ~AbstractObserver();  

  7.       

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

  9.   

  10.   

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

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

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

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

  15.     virtual void disable() = 0;  

  16. };  

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

[cpp] view plaincopy

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

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

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

[cpp] view plaincopy

  1. template <class C, class N>  

  2. class Observer: public AbstractObserver  

  3. {  

  4. public:  

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

  6.   

  7.   

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

  9.         _pObject(&object),   

  10.         _method(method)  

  11.     {  

  12.     }  

  13.       

  14.     Observer(const Observer& observer):  

  15.         AbstractObserver(observer),  

  16.         _pObject(observer._pObject),   

  17.         _method(observer._method)  

  18.     {  

  19.     }  

  20.       

  21.     ~Observer()  

  22.     {  

  23.     }  

  24.       

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

  26.     {  

  27.         if (&observer != this)  

  28.         {  

  29.             _pObject = observer._pObject;  

  30.             _method  = observer._method;  

  31.         }  

  32.         return *this;  

  33.     }  

  34.       

  35.     void notify(Notification* pNf) const  

  36.     {  

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

  38.   

  39.   

  40.         if (_pObject)  

  41.         {  

  42.             N* pCastNf = dynamic_cast(pNf);  

  43.             if (pCastNf)  

  44.             {  

  45.                 pCastNf->duplicate();  

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

  47.             }  

  48.         }  

  49.     }  

  50.       

  51.     bool equals(const AbstractObserver& abstractObserver) const  

  52.     {  

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

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

  55.     }  

  56.   

  57.   

  58.     bool accepts(Notification* pNf) const  

  59.     {  

  60.         return dynamic_cast(pNf) != 0;  

  61.     }  

  62.       

  63.     AbstractObserver* clone() const  

  64.     {  

  65.         return new Observer(*this);  

  66.     }  

  67.       

  68.     void disable()  

  69.     {  

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

  71.           

  72.         _pObject = 0;  

  73.     }  

  74.       

  75. private:  

  76.     Observer();  

  77.   

  78.   

  79.     C*       _pObject;  

  80.     Callback _method;  

  81.     mutable Poco::Mutex _mutex;  

  82. };  

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

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

3.4 一个使用例子

[cpp] view plaincopy

  1. #include "Poco/NotificationCenter.h"  

  2. #include "Poco/Notification.h"  

  3. #include "Poco/Observer.h"  

  4. #include "Poco/NObserver.h"  

  5. #include "Poco/AutoPtr.h"  

  6. #include   

  7. using Poco::NotificationCenter;  

  8. using Poco::Notification;  

  9. using Poco::Observer;  

  10. using Poco::NObserver;  

  11. using Poco::AutoPtr;  

  12. class BaseNotification: public Notification  

  13. {  

  14. };  

  15. class SubNotification: public BaseNotification  

  16. {  

  17. };  

  18.   

  19.   

  20. class Target  

  21. {  

  22. public:  

  23.     void handleBase(BaseNotification* pNf)  

  24.     {  

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

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

  27.     }  

  28.     void handleSub(const AutoPtr& pNf)  

  29.     {  

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

  31.     }  

  32. };  

  33.   

  34.   

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

  36. {  

  37.     NotificationCenter nc;  

  38.     Target target;  

  39.     nc.addObserver(  

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

  41.         );  

  42.     nc.addObserver(  

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

  44.         );  

  45.     nc.postNotification(new BaseNotification);  

  46.     nc.postNotification(new SubNotification);  

  47.     nc.removeObserver(  

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

  49.         );  

  50.     nc.removeObserver(  

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

  52.         );  

  53.     return 0;  

  54. }  

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

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

4. 异步通知

4.1 NotificationQueue类

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

[cpp] view plaincopy

  1. class Foundation_API NotificationQueue  

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

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

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

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

  6.     ///   

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

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

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

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

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

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

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

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

  15.     ///   3. join each worker thread  

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

  17. {  

  18. public:  

  19.     NotificationQueue();  

  20.         /// Creates the NotificationQueue.  

  21.   

  22.     ~NotificationQueue();  

  23.         /// Destroys the NotificationQueue.  

  24.   

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

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

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

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

  29.         /// a call like  

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

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

  32.           

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

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

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

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

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

  38.         /// a call like  

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

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

  41.   

  42.     Notification* dequeueNotification();  

  43.         /// Dequeues the next pending notification.  

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

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

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

  47.         ///  

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

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

  50.         /// memory management issues.  

  51.   

  52.     Notification* waitDequeueNotification();  

  53.         /// Dequeues the next pending notification.  

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

  55.         /// to be enqueued.   

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

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

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

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

  60.         ///  

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

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

  63.         /// memory management issues.  

  64.   

  65.     Notification* waitDequeueNotification(long milliseconds);  

  66.         /// Dequeues the next pending notification.  

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

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

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

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

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

  72.         ///  

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

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

  75.         /// memory management issues.  

  76.   

  77.     void dispatch(NotificationCenter& notificationCenter);  

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

  79.         /// notification center.  

  80.   

  81.     void wakeUpAll();  

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

  83.       

  84.     bool empty() const;  

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

  86.           

  87.     int size() const;  

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

  89.   

  90.     void clear();  

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

  92.           

  93.     bool hasIdleThreads() const;      

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

  95.         /// for a notification.  

  96.           

  97.     static NotificationQueue& defaultQueue();  

  98.         /// Returns a reference to the default  

  99.         /// NotificationQueue.  

100.   

101. protected:  

102.     Notification::Ptr dequeueOne();  

103.       

104. private:  

105.     typedef std::deque NfQueue;  

106.     struct WaitInfo  

107.     {  

108.         Notification::Ptr pNf;  

109.         Event             nfAvailable;  

110.     };  

111.     typedef std::deque WaitQueue;  

112.   

113.     NfQueue           _nfQueue;  

114.     WaitQueue         _waitQueue;  

115.     mutable FastMutex _mutex;  

116. };  

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

[cpp] view plaincopy

  1. Notification* NotificationQueue::waitDequeueNotification()  

  2. {  

  3.     Notification::Ptr pNf;  

  4.     WaitInfo* pWI = 0;  

  5.     {  

  6.         FastMutex::ScopedLock lock(_mutex);  

  7.         pNf = dequeueOne();  

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

  9.         pWI = new WaitInfo;  

  10.         _waitQueue.push_back(pWI);  

  11.     }  

  12.     pWI->nfAvailable.wait();  

  13.     pNf = pWI->pNf;  

  14.     delete pWI;  

  15.     return pNf.duplicate();  

  16. }  

  17.   

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

  19. {  

  20.     Notification::Ptr pNf;  

  21.     if (!_nfQueue.empty())  

  22.     {  

  23.         pNf = _nfQueue.front();  

  24.         _nfQueue.pop_front();  

  25.     }  

  26.     return pNf;  

  27. }  

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

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

[cpp] view plaincopy

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

  2. {  

  3.     poco_check_ptr (pNotification);  

  4.     FastMutex::ScopedLock lock(_mutex);  

  5.     if (_waitQueue.empty())  

  6.     {  

  7.         _nfQueue.push_back(pNotification);  

  8.     }  

  9.     else  

  10.     {  

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

  12.         _waitQueue.pop_front();  

  13.         pWI->pNf = pNotification;  

  14.         pWI->nfAvailable.set();  

  15.     }     

  16. }  

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

4.2 一个异步通知的例子

[cpp] view plaincopy

  1. #include "Poco/Notification.h"  

  2. #include "Poco/NotificationQueue.h"  

  3. #include "Poco/ThreadPool.h"  

  4. #include "Poco/Runnable.h"  

  5. #include "Poco/AutoPtr.h"  

  6. using Poco::Notification;  

  7. using Poco::NotificationQueue;  

  8. using Poco::ThreadPool;  

  9. using Poco::Runnable;  

  10. using Poco::AutoPtr;  

  11. class WorkNotification: public Notification  

  12. {  

  13. public:  

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

  15.     int data() const  

  16.     {  

  17.         return _data;  

  18.     }  

  19. private:  

  20.     int _data;  

  21. };  

  22.   

  23.   

  24. class Worker: public Runnable  

  25. {  

  26. public:  

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

  28.     void run()  

  29.     {  

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

  31.         while (pNf)  

  32.         {  

  33.             WorkNotification* pWorkNf =  

  34.                 dynamic_cast(pNf.get());  

  35.             if (pWorkNf)  

  36.             {  

  37.                 // do some work  

  38.             }  

  39.             pNf = _queue.waitDequeueNotification();  

  40.         }  

  41.     }  

  42. private:  

  43.     NotificationQueue& _queue;  

  44. };  

  45.   

  46.   

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

  48. {  

  49.     NotificationQueue queue;  

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

  51.     Worker worker2(queue);  

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

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

  54.     // create some work  

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

  56.     {  

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

  58.     }  

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

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

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

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

  63.     return 0;  

  64. }  

4.3 异步通知的类图

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

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

5. 事件

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

5.1 从例子说起

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

[cpp] view plaincopy

  1. #include "Poco/BasicEvent.h"  

  2. #include "Poco/Delegate.h"  

  3. #include   

  4.   

  5. using Poco::BasicEvent;  

  6. using Poco::Delegate;  

  7.   

  8. class Source  

  9. {  

  10. public:  

  11.     BasicEvent<int> theEvent;  

  12.     void fireEvent(int n)  

  13.     {  

  14.         theEvent(this, n);  

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

  16.     }  

  17. };  

  18.   

  19. class Target  

  20. {  

  21. public:  

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

  23.     {  

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

  25.     }  

  26. };  

  27.   

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

  29. {  

  30.     Source source;  

  31.     Target target;  

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

  33.     source.fireEvent(42);  

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

  35.   

  36.     return 0;  

  37. }  

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

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

[cpp] view plaincopy

  1. void fireEvent(int n)  

  2. {  

  3.     theEvent(this, n);  

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

  5. }  

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

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

[cpp] view plaincopy

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

        2. 仿函式

[cpp] view plaincopy

  1. theEvent(this, n);  

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

[cpp] view plaincopy

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

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

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

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

5.2 事件的实现

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

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

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

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

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

        Delegate类定义如下:

[cpp] view plaincopy

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

  2. class Delegate: public AbstractDelegate  

  3. {  

  4. public:  

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

  6.   

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

  8.         _receiverObject(obj),   

  9.         _receiverMethod(method)  

  10.     {  

  11.     }  

  12.   

  13.     Delegate(const Delegate& delegate):  

  14.         AbstractDelegate(delegate),  

  15.         _receiverObject(delegate._receiverObject),  

  16.         _receiverMethod(delegate._receiverMethod)  

  17.     {  

  18.     }  

  19.   

  20.     ~Delegate()  

  21.     {  

  22.     }  

  23.       

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

  25.     {  

  26.         if (&delegate != this)  

  27.         {  

  28.             this->_receiverObject = delegate._receiverObject;  

  29.             this->_receiverMethod = delegate._receiverMethod;  

  30.         }  

  31.         return *this;  

  32.     }  

  33.   

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

  35.     {  

  36.         Mutex::ScopedLock lock(_mutex);  

  37.         if (_receiverObject)  

  38.         {  

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

  40.             return true;  

  41.         }  

  42.         else return false;  

  43.     }  

  44.   

  45.     bool equals(const AbstractDelegate& other) const  

  46.     {  

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

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

  49.     }  

  50.   

  51.     AbstractDelegate* clone() const  

  52.     {  

  53.         return new Delegate(*this);  

  54.     }  

  55.       

  56.     void disable()  

  57.     {  

  58.         Mutex::ScopedLock lock(_mutex);  

  59.         _receiverObject = 0;  

  60.     }  

  61.   

  62. protected:  

  63.     TObj*        _receiverObject;  

  64.     NotifyMethod _receiverMethod;  

  65.     Mutex        _mutex;  

  66.   

  67.   

  68. private:  

  69.     Delegate();  

  70. };  

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

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

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

[cpp] view plaincopy

  1. template <class TObj, class TArgs>  

  2. static 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  

  3.     /// Default notification strategy.  

  4.     ///  

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

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

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

  8. {  

  9. public:  

  10.     typedef SharedPtr         DelegatePtr;  

  11.     typedef std::vector     Delegates;  

  12.     typedef typename Delegates::iterator Iterator;  

  13.   

  14. public:  

  15.     DefaultStrategy()  

  16.     {  

  17.     }  

  18.   

  19.     DefaultStrategy(const DefaultStrategy& s):  

  20.         _delegates(s._delegates)  

  21.     {  

  22.     }  

  23.   

  24.     ~DefaultStrategy()  

  25.     {  

  26.     }  

  27.   

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

  29.     {  

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

  31.         {  

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

  33.         }  

  34.     }  

  35.   

  36.     void add(const TDelegate& delegate)  

  37.     {  

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

  39.     }  

  40.   

  41.     void remove(const TDelegate& delegate)  

  42.     {  

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

  44.         {  

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

  46.             {  

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

  48.                 _delegates.erase(it);  

  49.                 return;  

  50.             }  

  51.         }  

  52.     }  

  53.   

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

  55.     {  

  56.         if (this != &s)  

  57.         {  

  58.             _delegates = s._delegates;  

  59.         }  

  60.         return *this;  

  61.     }  

  62.   

  63.     void clear()  

  64.     {  

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

  66.         {  

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

  68.         }  

  69.         _delegates.clear();  

  70.     }  

  71.   

  72.     bool empty() const  

  73.     {  

  74.         return _delegates.empty();  

  75.     }  

  76.   

  77. protected:  

  78.     Delegates _delegates;  

  79. };  

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

[cpp] view plaincopy

  1. std::vector>  _delegate  

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

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

[cpp] view plaincopy

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

  2. class BasicEvent: public AbstractEvent <   

  3.     TArgs, DefaultStrategy >,  

  4.     AbstractDelegate,  

  5.     TMutex  

  6. >  

  7.     /// A BasicEvent uses the DefaultStrategy which   

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

  9.     ///  

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

  11.     /// for more information.  

  12. {  

  13. public:  

  14.     BasicEvent()  

  15.     {  

  16.     }  

  17.   

  18.     ~BasicEvent()  

  19.     {  

  20.     }  

  21.   

  22. private:  

  23.     BasicEvent(const BasicEvent& e);  

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

  25. };  

  26.   

  27. AbstractEvent定义为:  

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

  29. class AbstractEvent  

  30. {  

  31. public:  

  32.     AbstractEvent():   

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

  34.         _enabled(true)  

  35.     {  

  36.     }  

  37.   

  38.     AbstractEvent(const TStrategy& strat):   

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

  40.         _strategy(strat),  

  41.         _enabled(true)  

  42.     {     

  43.     }  

  44.   

  45.     virtual ~AbstractEvent()  

  46.     {  

  47.     }  

  48.   

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

  50.     {  

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

  52.         _strategy.add(aDelegate);  

  53.     }  

  54.       

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

  56.     {  

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

  58.         _strategy.remove(aDelegate);  

  59.     }  

  60.       

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

  62.     {  

  63.         notify(pSender, args);  

  64.     }  

  65.       

  66.     void operator () (TArgs& args)  

  67.     {  

  68.         notify(0, args);  

  69.     }  

  70.   

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

  72.     {  

  73.         Poco::ScopedLockWithUnlock lock(_mutex);  

  74.           

  75.         if (!_enabled) return;  

  76.           

  77.         TStrategy strategy(_strategy);  

  78.         lock.unlock();  

  79.         strategy.notify(pSender, args);  

  80.     }  

  81.   

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

  83.     {  

  84.         NotifyAsyncParams params(pSender, args);  

  85.         {  

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

  87.                   

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

  89.             params.enabled  = _enabled;  

  90.         }  

  91.         ActiveResult result = _executeAsync(params);  

  92.         return result;  

  93.     }  

  94.         // …….  

  95.   

  96. protected:  

  97.     struct NotifyAsyncParams  

  98.     {  

  99.         SharedPtr ptrStrat;  

100.         const void* pSender;  

101.         TArgs       args;  

102.         bool        enabled;  

103.           

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

105.         {  

106.         }  

107.     };  

108.   

109.     ActiveMethod _executeAsync;  

110.   

111.     TArgs executeAsyncImpl(const NotifyAsyncParams& par)  

112.     {  

113.         if (!par.enabled)  

114.         {  

115.             return par.args;  

116.         }  

117.   

118.   

119.         NotifyAsyncParams params = par;  

120.         TArgs retArgs(params.args);  

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

122.         return retArgs;  

123.     }  

124.   

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

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

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

128.     mutable TMutex _mutex;  

129.   

130. private:  

131.     AbstractEvent(const AbstractEvent& other);  

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

133. };  

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

5.2 异步事件

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

[cpp] view plaincopy

  1. #include "Poco/BasicEvent.h"  

  2. #include "Poco/Delegate.h"  

  3. #include "Poco/ActiveResult.h"  

  4. #include   

  5.   

  6. using Poco::BasicEvent;  

  7. using Poco::Delegate;  

  8. using Poco::ActiveResult;  

  9.   

  10. class TargetAsync  

  11. {  

  12. public:  

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

  14.     {  

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

  16.         return;  

  17.     }  

  18. };  

  19.   

  20. template<typename RT> class Source  

  21. {  

  22. public:  

  23.     BasicEvent<int> theEvent;  

  24.     ActiveResult AsyncFireEvent(int n)  

  25.     {  

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

  27.     }  

  28. };  

  29.   

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

  31. {  

  32.     Source<int> source;  

  33.     TargetAsync target;  

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

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

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

  37.     Targs.wait();  

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

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

  40.   

  41.     return 0;  

  42. }  

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

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

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

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

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

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

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

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

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

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

1. ByteOrder

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

1.1 big-endian和litter-endian

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

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

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

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

1.2 主机序和网络序

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

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

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

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

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

1.4 ByteOrder静态函数

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

             下面来看一个ByteOrder的例子:

[cpp] view plaincopy

  1. #include "Poco/ByteOrder.h"  

  2. #include   

  3. using Poco::ByteOrder;  

  4. using Poco::UInt16;  

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

  6. {  

  7. #ifdef POCO_ARCH_LITTLE_ENDIAN  

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

  9. #else  

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

  11. #endif  

  12.     UInt16 port = 80;  

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

  14.     return 0;  

  15. }  

2. Any

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

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

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

[cpp] view plaincopy

  1. #include "Poco/Any.h"  

  2. #include "Poco/Exception.h"  

  3. #include   

  4.   

  5. using Poco::Any;  

  6. using Poco::AnyCast;  

  7. using Poco::RefAnyCast;  

  8.   

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

  10. {  

  11.     Any any(42);  

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

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

  14.     try  

  15.     {  

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

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

  18.     }  

  19.     catch (Poco::BadCastException&)  

  20.     {}  

  21.     return 0;  

  22. }  

         最后给出Poco::Any的类图

3. DynamicAny

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

[cpp] view plaincopy

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

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

  3.         ///  

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

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

  6.         /// is thrown.  

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

  8. {  

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

  10.     {  

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

  12.         return pHolderImpl->value();  

  13.     }  

  14.     else if (!_pHolder)  

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

  16.     else  

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

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

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

  20. }  

[cpp] view plaincopy

  1. template <typename ValueType>  

  2. ValueType* AnyCast(Any* operand)  

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

  4.   

  5.   

  6. pointer  

  7.     /// to the stored value.   

  8.     ///  

  9.     /// Example Usage:   

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

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

  12. {  

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

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

  15.                 : 0;  

  16. }  

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

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

         下面是Poco::DynamicAny的类图:

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

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

1. Hash概论

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

1.1 定义

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

1.2 Hash table查找效率

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

1.3 Poco中的Hash内容

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

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

2. Hash函数

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

[cpp] view plaincopy

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

  2. {  

  3.     return static_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. * The basis of the hash algorithm was taken from an idea sent by email to the 

  34. * IEEE Posix P1003.2 mailing list from Phong Vo (kpv@research.att.com) and 

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

  36. * later improved on their algorithm. 

  37. * The magic is in the interesting relationship between the special prime 

  38. * 16777619 (2^24 + 403) and 2^32 and 2^8. 

  39. * This hash produces the fewest collisions of any function that we've seen so 

  40. * far, and works well on both numbers and strings. 

  41. */  

  42.   

  43. uint calc_hashnr(const byte *key, uint len)  

  44. {  

  45.     const byte *end=key+len;  

  46.     uint hash;  

  47.     for (hash = 0; key < end; key++)  

  48.     {  

  49.         hash *= 16777619;  

  50.         hash ^= (uint) *(uchar*) key;  

  51.     }  

  52.     return (hash);  

  53. }  

  54.   

  55. uint calc_hashnr_caseup(const byte *key, uint len)  

  56. {  

  57.     const byte *end=key+len;  

  58.     uint hash;  

  59.     for (hash = 0; key < end; key++)  

  60.     {  

  61.         hash *= 16777619;  

  62.         hash ^= (uint) (uchar) toupper(*key);  

  63.     }  

  64.     return (hash);  

  65. }  

  66. #endif  

3. Hash 表

       我们接下去分析Poco中Hash表的实现。Poco中实现了3种哈希表,分别是SimpleHashTable, HashTable,LinearHashTable。它们的实现对应了当出现冲突时,解决冲突的不同方法。首先我们看一下通用的解决方法。
       1. 线性探测。当出现碰撞时,顺序依次查询后续位置,直到找到空位。《利用线性探测法构造散列表》
       2. 双重散列法。当使用第一个散列Hash函数,出现碰撞时,用第二个散列函数去寻找空位
       3. 拉链法。出现碰撞的时候,使用list存储碰撞数据
       4. 线性哈希,linear hash。立刻分裂或者延迟分裂。通过分裂,控制桶的高度,每次分裂时,会重新散列碰撞元素。《linearhashing》

       SimpleHashTable的实现对应了方法一;HashTable对应了方法3;LinearHashTable对应了方法4。

3.1 SimpleHashTable

       从类图里我们看到,SimpleHashTable是一个HashEntry容器, 内部定义如下:

[cpp] view plaincopy

  1. std::vector _entries  

       当插入新数据时,首先根据hash值,计算空位,然后存储;如果发现冲突,顺着计算的hash值按地址顺序依次寻找空位;如_entries容器无空位,则抛出异常。

[cpp] view plaincopy

  1. UInt32 insert(const Key& key, const Value& value)  

  2. /// Returns the hash value of the inserted item.  

  3. /// Throws an exception if the entry was already inserted  

  4. {  

  5.     UInt32 hsh = hash(key);  

  6.     insertRaw(key, hsh, value);  

  7.     return hsh;  

  8. }  

  9.   

  10. Value& insertRaw(const Key& key, UInt32 hsh, const Value& value)  

  11. /// Returns the hash value of the inserted item.  

  12. /// Throws an exception if the entry was already inserted  

  13. {  

  14.     UInt32 pos = hsh;  

  15.     if (!_entries[pos])  

  16.         _entries[pos] = new HashEntry(key, value);  

  17.     else  

  18.     {  

  19.         UInt32 origHash = hsh;  

  20.         while (_entries[hsh % _capacity])  

  21.         {  

  22.             if (_entries[hsh % _capacity]->key == key)  

  23.                 throw ExistsException();  

  24.             if (hsh - origHash > _capacity)  

  25.                 throw PoolOverflowException("SimpleHashTable full");  

  26.             hsh++;  

  27.         }  

  28.         pos = hsh % _capacity;  

  29.         _entries[pos] = new HashEntry(key, value);  

  30.     }  

  31.     _size++;  

  32.     return _entries[pos]->value;  

  33. }  

       SimpleHashTable进行搜索时,策略也一致。

[cpp] view plaincopy

  1. const Value& get(const Key& key) const  

  2. /// Throws an exception if the value does not exist  

  3. {  

  4.     UInt32 hsh = hash(key);  

  5.     return getRaw(key, hsh);  

  6. }  

  7.   

  8. const Value& getRaw(const Key& key, UInt32 hsh) const  

  9. /// Throws an exception if the value does not exist  

  10. {  

  11.     UInt32 origHash = hsh;  

  12.     while (true)  

  13.     {  

  14.         if (_entries[hsh % _capacity])  

  15.         {  

  16.             if (_entries[hsh % _capacity]->key == key)  

  17.             {  

  18.                 return _entries[hsh % _capacity]->value;  

  19.             }  

  20.         }  

  21.         else  

  22.             throw InvalidArgumentException("value not found");  

  23.         if (hsh - origHash > _capacity)  

  24.             throw InvalidArgumentException("value not found");  

  25.         hsh++;  

  26.     }  

  27. }  

       SimpleHashTable没有提供删除数据的接口,只适用于数据量不大的简单应用。

3.2 HashTable

       HashTable是拉链法的一个变种。当冲突数据发生时,存储的容器是map而不是list。其内部容器定义为:

[cpp] view plaincopy

  1. HashEntryMap** _entries;  

       同map相比,它实际上是把一个大map分成了很多个小map,通过hash方法寻找到小map,再通过map的find函数寻找具体数据。其插入和搜索数据函数如下:

[cpp] view plaincopy

  1. UInt32 insert(const Key& key, const Value& value)  

  2. /// Returns the hash value of the inserted item.  

  3. /// Throws an exception if the entry was already inserted  

  4. {  

  5.     UInt32 hsh = hash(key);  

  6.     insertRaw(key, hsh, value);  

  7.     return hsh;  

  8. }  

  9.   

  10.   

  11. Value& insertRaw(const Key& key, UInt32 hsh, const Value& value)  

  12. /// Returns the hash value of the inserted item.  

  13. /// Throws an exception if the entry was already inserted  

  14. {  

  15.     if (!_entries[hsh])  

  16.         _entries[hsh] = new HashEntryMap();  

  17.     std::pair<typename HashEntryMap::iterator, bool> res(_entries[hsh]->insert(std::make_pair(key, value)));  

  18.     if (!res.second)  

  19.         throw InvalidArgumentException("HashTable::insert, key already exists.");  

  20.     _size++;  

  21.     return res.first->second;  

  22. }  

  23.   

  24.   

  25. const Value& get(const Key& key) const  

  26. /// Throws an exception if the value does not exist  

  27. {  

  28.     UInt32 hsh = hash(key);  

  29.     return getRaw(key, hsh);  

  30. }  

  31.   

  32.   

  33. const Value& getRaw(const Key& key, UInt32 hsh) const  

  34. /// Throws an exception if the value does not exist  

  35. {  

  36.     if (!_entries[hsh])  

  37.         throw InvalidArgumentException("key not found");  

  38.   

  39.     ConstIterator it = _entries[hsh]->find(key);  

  40.     if (it == _entries[hsh]->end())  

  41.         throw InvalidArgumentException("key not found");  

  42.   

  43.     return it->second;  

  44. }  

       HashTable支持remove操作。

3.2 LinearHashTable

       LinearHashTable按照解决冲突的方法4实现。它内部的容器为vector>,同时还存在两个控制量_split和_front:

[cpp] view plaincopy

  1. std::size_t _split;  

  2. std::size_t _front;  

  3. vector> _buckets;  

       它的插入操作如下:

[cpp] view plaincopy

  1. std::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, TEventMutex > Add;  

  7.     FIFOEvent<const KeyValueArgs, TEventMutex > Update;  

  8.     FIFOEvent<const TKey, TEventMutex>                         Remove;  

  9.     FIFOEvent<const TKey, TEventMutex>                         Get;  

  10.     FIFOEvent<const EventArgs, TEventMutex>                    Clear;  

  11.   

  12.     typedef std::map > DataHolder;  

  13.     typedef typename DataHolder::iterator       Iterator;  

  14.     typedef typename DataHolder::const_iterator ConstIterator;  

  15.     typedef std::set                      KeySet;  

  16.   

  17.     AbstractCache()  

  18.     {  

  19.         initialize();  

  20.     }  

  21.   

  22.     AbstractCache(const TStrategy& strat): _strategy(strat)  

  23.     {  

  24.         initialize();  

  25.     }  

  26.   

  27.     virtual ~AbstractCache()  

  28.     {  

  29.         uninitialize();  

  30.     }  

  31.   

  32.         // ………..  

  33.   

  34. protected:  

  35.     mutable FIFOEvent > IsValid;  

  36.     mutable FIFOEvent           Replace;  

  37.   

  38.     void initialize()  

  39.         /// Sets up event registration.  

  40.     {  

  41.         Add     += Delegateconst KeyValueArgs >(&_strategy, &TStrategy::onAdd);  

  42.         Update  += Delegateconst KeyValueArgs >(&_strategy, &TStrategy::onUpdate);  

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

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

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

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

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

  48.     }  

  49.   

  50.     void uninitialize()  

  51.         /// Reverts event registration.  

  52.     {  

  53.         Add     -= Delegateconst KeyValueArgs >(&_strategy, &TStrategy::onAdd );  

  54.         Update  -= Delegateconst KeyValueArgs >(&_strategy, &TStrategy::onUpdate);  

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

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

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

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

  59.         Replace -= Delegate(&_strategy, &TStrategy::onReplace);  

  60.     }  

  61.   

  62.     void doAdd(const TKey& key, const TValue& val)  

  63.         /// Adds the key value pair to the cache.  

  64.         /// If for the key already an entry exists, it will be overwritten.  

  65.     {  

  66.         Iterator it = _data.find(key);  

  67.         doRemove(it);  

  68.   

  69.   

  70.         KeyValueArgs args(key, val);  

  71.         Add.notify(this, args);  

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

  73.           

  74.         doReplace();  

  75.     }  

  76.   

  77.     void doAdd(const TKey& key, SharedPtr& val)  

  78.         /// Adds the key value pair to the cache.  

  79.         /// If for the key already an entry exists, it will be overwritten.  

  80.     {  

  81.         Iterator it = _data.find(key);  

  82.         doRemove(it);  

  83.   

  84.   

  85.         KeyValueArgs args(key, *val);  

  86.         Add.notify(this, args);  

  87.         _data.insert(std::make_pair(key, val));  

  88.           

  89.         doReplace();  

  90.     }  

  91.   

  92.     void doUpdate(const TKey& key, const TValue& val)  

  93.         /// Adds the key value pair to the cache.  

  94.         /// If for the key already an entry exists, it will be overwritten.  

  95.     {  

  96.         KeyValueArgs args(key, val);  

  97.         Iterator it = _data.find(key);  

  98.         if (it == _data.end())  

  99.         {  

100.             Add.notify(this, args);  

101.             _data.insert(std::make_pair(key, SharedPtr(new TValue(val))));  

102.         }  

103.         else  

104.         {  

105.             Update.notify(this, args);  

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

107.         }  

108.           

109.         doReplace();  

110.     }  

111.   

112.     void doUpdate(const TKey& key, SharedPtr& val)  

113.         /// Adds the key value pair to the cache.  

114.         /// If for the key already an entry exists, it will be overwritten.  

115.     {  

116.         KeyValueArgs args(key, *val);  

117.         Iterator it = _data.find(key);  

118.         if (it == _data.end())  

119.         {  

120.             Add.notify(this, args);  

121.             _data.insert(std::make_pair(key, val));  

122.         }  

123.         else  

124.         {  

125.             Update.notify(this, args);  

126.             it->second = val;  

127.         }  

128.           

129.         doReplace();  

130.     }  

131.   

132.     void doRemove(Iterator it)   

133.         /// Removes an entry from the cache. If the entry is not found  

134.         /// the remove is ignored.  

135.     {  

136.         if (it != _data.end())  

137.         {  

138.             Remove.notify(this, it->first);  

139.             _data.erase(it);  

140.         }  

141.     }  

142.   

143.     bool doHas(const TKey& key) const  

144.         /// Returns true if the cache contains a value for the key  

145.     {  

146.         // ask the strategy if the key is valid  

147.         ConstIterator it = _data.find(key);  

148.         bool result = false;  

149.   

150.   

151.         if (it != _data.end())  

152.         {  

153.             ValidArgs args(key);  

154.             IsValid.notify(this, args);  

155.             result = args.isValid();  

156.         }  

157.   

158.         return result;  

159.     }  

160.   

161.     SharedPtr doGet(const TKey& key)   

162.         /// Returns a SharedPtr of the cache entry, returns 0 if for  

163.         /// the key no value was found  

164.     {  

165.         Iterator it = _data.find(key);  

166.         SharedPtr result;  

167.   

168.         if (it != _data.end())  

169.         {     

170.             // inform all strategies that a read-access to an element happens  

171.             Get.notify(this, key);  

172.             // ask all strategies if the key is valid  

173.             ValidArgs args(key);  

174.             IsValid.notify(this, args);  

175.   

176.             if (!args.isValid())  

177.             {  

178.                 doRemove(it);  

179.             }  

180.             else  

181.             {  

182.                 result = it->second;  

183.             }  

184.         }  

185.   

186.         return result;  

187.     }  

188.   

189.     void doClear()  

190.     {  

191.         static EventArgs _emptyArgs;  

192.         Clear.notify(this, _emptyArgs);  

193.         _data.clear();  

194.     }  

195.   

196.     void doReplace()  

197.     {  

198.         std::set delMe;  

199.         Replace.notify(this, delMe);  

200.         // delMe contains the to be removed elements  

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

202.         typename std::set::const_iterator endIt = delMe.end();  

203.   

204.         for (; it != endIt; ++it)  

205.         {  

206.             Iterator itH = _data.find(*it);  

207.             doRemove(itH);  

208.         }  

209.     }  

210.   

211.     TStrategy          _strategy;  

212.     mutable DataHolder _data;  

213.     mutable TMutex  _mutex;  

214.   

215. private:  

216.     // ….  

217. };  

        从上面的定义中,可以看到AbstractCache是一个value的容器,采用map保存数据,

[cpp] view plaincopy

  1. mutable std::map > _data;  

        另外AbstractCache中还定义了一个TStrategy对象,

[cpp] view plaincopy

  1. TStrategy          _strategy;  

        并且在AbstractCache的initialize()函数中,把Cache的一些函数操作委托给TStrategy对象。其函数操作接口为:
        1. Add : 向容器中添加元素
        2. Update : 更新容器中元素
        3. Remove : 删除容器中元素
        4. Get : 获取容器中元素
        5. Clear : 清除容器中所有元素
        6. IsValid: 容器中是否某元素
        7. Replace: 按照策略从strategy中获取过期元素,并从Cache和Strategy中同时删除。将触发一系列的Remove函数。

        这几个操作中最复杂的是Add操作,其中包括了Remove、Insert和Replace操作。

[cpp] view plaincopy

  1. void doAdd(const TKey& key, SharedPtr& val)  

  2.     /// Adds the key value pair to the cache.  

  3.     /// If for the key already an entry exists, it will be overwritten.  

  4. {  

  5.     Iterator it = _data.find(key);  

  6.     doRemove(it);  

  7.   

  8.   

  9.     KeyValueArgs args(key, *val);  

  10.     Add.notify(this, args);  

  11.     _data.insert(std::make_pair(key, val));  

  12.           

  13.     doReplace();  

  14. }  

        而Replace操作可被Add、Update、Get操作触发。这是因为Cache并不是一个主动对象(POCO C++库学习和分析 -- 线程(四)),不会自动的把元素标志为失效,需要外界也就是调用方触发进行。

        在Cache类中另外一个值得注意的地方是,保存的是TValue的SharedPtr。之所以这么设计,是为了线程安全,由于replace操作可能被多个线程调用,所以解决的方法,要么是返回TValue的SharedPtr,要么是返回TValue的拷贝。同拷贝方法相比,SharedPtr的方法要更加廉价。

2.2 Strategy类

       Strategy类完成了对_data中保存的pair中key的排序工作。每个Strategy中都存在一个key的容器,其中LRUStrategy中是std::list,ExpireStrategy、UniqueAccessExpireStrategy、UniqueExpireStrategy中是std::multimap

       对于LRU策略,这么设计我是可以理解的。每次访问都会使key被重置于list的最前端。为了实现对list快速访问,增加一个std::map容器,每次对list容器进行插入操作时,把插入位的itorator保存入map中,这样对于list的访问效率可以从O(n)变成O(log(n)),因为不需要遍历了。下面是相关的代码:

[cpp] view plaincopy

  1. void onReplace(const void*, std::set& elemsToRemove)  

  2. {  

  3.     // Note: replace only informs the cache which elements  

  4.     // it would like to remove!  

  5.     // it does not remove them on its own!  

  6.     std::size_t curSize = _keyIndex.size();  

  7.   

  8.     if (curSize < _size)  

  9.     {  

  10.         return;  

  11.     }  

  12.   

  13.     std::size_t diff = curSize - _size;  

  14.     Iterator it = --_keys.end(); //--keys can never be invoked on an empty list due to the minSize==1 requirement of LRU  

  15.     std::size_t i = 0;  

  16.   

  17.     while (i++ < diff)   

  18.     {  

  19.         elemsToRemove.insert(*it);  

  20.         if (it != _keys.begin())  

  21.         {  

  22.             --it;  

  23.         }  

  24.     }  

  25. }  

        LRUStrategy的replace操作是,只在curSize超过设定的访问上限_size时触发,把list容器中排在末尾的(curSize-_size)个元素标志为失效。

        而对于Time base expired策略,还如此设计,我觉得不太合适。在时间策略的strategy类中,存在着两个容器,一个是std::map,另外一个是std::multimap。进行插入操作时,代码为:

[cpp] view plaincopy

  1. void onAdd(const void*, const KeyValueArgs & args)  

  2. {  

  3.     Timestamp now;  

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

  5.     std::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& elemsToRemove)  

  2. {  

  3.     // Note: replace only informs the cache which elements  

  4.     // it would like to remove!  

  5.     // it does not remove them on its own!  

  6.     IndexIterator it = _keyIndex.begin();  

  7.     while (it != _keyIndex.end() && it->first.isElapsed(_expireTime))  

  8.     {  

  9.         elemsToRemove.insert(it->second);  

  10.         ++it;  

  11.     }  

  12. }  

        可以看到这是对multimap的遍历,效率为O(n)。

        如果这样的话,我觉得完全可以把std::map和std::multimap合二为一,定义成为std::map,replace的操作仍然采用遍历,效率为O(n).
        对于基于时间的策略,O(n)的效率可能不能接受。我觉得可能的解决方法有两种。第一,把Cache变成主动对象,内部定期的收集失效元素,而不由外部触发。这样虽然并没有提高replace操作效率,但把replace操作和外部接口的add等操作分开了。外部调用接口的效率提高了。第二,在内部实现多个map容器,分组管理不同过期时间的对象。

3. 开销

        Poco中的Cache类比std::map要慢,其中开销最大的操作为add操作。采用Time Expire策略的Cache要比采用LRU策略的Cache更慢。并且由于Cache类引入了SharePtr和Strategy,其空间花费也要大于std::map。所以在没有必要使用Cache的情况下,还是使用map较好。

4. 例子

        下面是Cache的一个示例:

[cpp] view plaincopy

  1. #include "Poco/LRUCache.h"  

  2. int main()  

  3. {  

  4.     Poco::LRUCache<int, std::string> myCache(3);  

  5.     myCache.add(1, "Lousy"); // |-1-| -> first elem is the most popular one  

  6.     Poco::SharedPtr ptrElem = myCache.get(1); // |-1-|  

  7.     myCache.add(2, "Morning"); // |-2-1-|  

  8.     myCache.add(3, "USA"); // |-3-2-1-|  

  9.     // now get rid of the most unpopular entry: "Lousy"  

  10.     myCache.add(4, "Good"); // |-4-3-2-|  

  11.     poco_assert (*ptrElem == "Lousy"); // content of ptrElem is still valid  

  12.     ptrElem = myCache.get(2); // |-2-4-3-|  

  13.     // replace the morning entry with evening  

  14.     myCache.add(2, "Evening"); // 2 Events: Remove followed by Add  

  15. }  

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

1. 字符编码

1.1 字符编码的概念

        字符编码可以理解为在计算机上语言符号和二比特数之间的映射。不同的编码方式对应着不同映射方法,对于映射集的双方而言,用一种映射方法下,映射关系是一一对应的。由于语言的基本符号是有限的,所以作为映射的双方,映射集也是有限的。下面这段概念的介绍来自于文章《字符编码:Unicode/UTF-8/UTF-16/UCS/Endian/BMP/BOM》、《C++字符串完全指引(ZT)》、《字符编码笔记:ASCII,Unicode和UTF-8》,并混杂了一些自己的理解。

1. ASCII码 

        对于英文字母而言,语言的符号是26个字母,因此在上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。ASCII码一共规定了128个字符的编码,比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。 

2. 非ASCII编码 

        英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。 
        但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0—127表示的符号是一样的,不一样的只是128—255的这一段。 
        至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256×256=65536个符号。中文编码的问题需要专文讨论,这篇笔记不涉及。这里只指出,虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。 

3. 各自定义非ASCII编码的问题

        由于各国都制定了自己的兼容ascii编码规范,就是各种ANSI码,比如我国的gb2312,用两个扩展ascii字符来表示一个中文。这带来了一个新问题,这些ansi码无法同时存在,因为它们的定义互相重叠,要自由使用不同语言就必须有一个新编码,为各种文字统一分配编码。 

4. 微软的解决方案

        微软为了解决这一问题,提出了一个自己的解决方案。windows上的MBCS方法,在 MBCS 下,字符被编码为单字节或双字节。在双字节字符中,第一个字节(即前导字节)表示它和下一个字节将被解释为一个字符。第一个字节来自留作前导字节的代码范围。哪个范围的字节可以用作前导字节取决于所使用的代码页。例如,日文代码页 932 使用 0x81 到 0x9F 范围内的字节作为前导字节,而朝鲜语代码页 949 则使用其他范围的字节。

5. 另一种解决方案Unicode

        虽然微软提了自己的方案,其他人也没闲着。为了解决这一问题。国际标准化组织(ISO)想出了一个办法,这个办法其实和微软也类似。即存在有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。 
        Unicode是一种字符编码方法,它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是”UniversalMultiple-Octet Coded Character Set
”,简称为UCS。UCS可以看作是”Unicode Character Set”的缩写。 

       Unicode现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字“严”。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。 
        根据维基百科的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。 在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。 目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。 

6. UCS-2、UCS-4、BMP 

        UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏: 
        UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。 
        UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。 
        group 0的plane 0被称作BasicMultilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。 
        将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。
        由于即使是老UCS-2,也可以表示2^16=65535个字符,基本上可以容纳所有常用各国字符,所以目前各国基本都使用UCS-2。  

7. Unicode的问题 

        值得注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。 
        比如,汉字“严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。 
        这里就有两个严重的问题,第一个问题是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。 
        它们造成的结果是:1)出现了unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示unicode。2)unicode在很长一段时间内无法推广,直到互联网的出现。 
        怎样存储和传输这些编码,是由UTF(UCS TransformationFormat)规范规定的,常见的UTF规范包括UTF-8、UTF-7、UTF-16、UTF-32。

8. UTF编码

        UTF(UCS Transformation Format)规范设计时考虑了一些现实问题。即在UCS定义之前,已经存在大量的ASCII程序。新定义的UCS的表示方法必须兼容原始的ascii程序和方法。
        这个问题也可以表示为,Unicode使用2个字节表示一个字符,ascii使用1个字节,在很多方面产生了冲突,以前处理ascii的方法都必须重写。而且C语言用\0作为字符串结束标志,但Unicode中很多字符都含\0,C语言的字符串函数也无法正常处理Unicode。为了把unicode投入实用,出现了UTF,最常见的是UTF-8、UTF-16和UTF-32。

        其中UTF-16和Unicode本身的编码是一致的,UTF-32和UCS-4也是相同的,但最重要的是UTF-8编码方式。(UTF-32中字符的数量为2^32,也就是说用一个4 byte的int值可以表示一个人类字符。一个int值既然可以可以表示所有UCS-4中的字符,当然也可以表示UCS-2中对应的所有字符)。那为什么会出现UTF-8编码方式呢。UTF8是一种变长的编码,它的字节数是不固定的,使用第一个字节确定字节数。第一个字节首为0即一个字节,110即2字节,1110即3字节,字符后续字节都用10开始,这样不会混淆且单字节英文字符可仍用ASCII编码。理论上UTF-8最大可以用6字节表示一个字符,但Unicode目前没有用大于0xffff的字符,实际UTF-8最多使用了3个字节。 

        UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下: 

UCS-2编码(16进制)

bit数

UTF-8 字节流(二进制)

byte数

备注

0000 0000 ~

0000 007F

0~7

0XXX XXXX

1

0000 0080 ~

0000 07FF

8~11

110X XXXX

10XX XXXX

2

0000 0800 ~

0000 FFFF

12~16

1110 XXXX

10XX XXXX

10XX XXXX

3

基本定义范围:0~FFFF

0001 0000 ~

001F FFFF

17~21

1111 0XXX

10XX XXXX

10XX XXXX

10XX XXXX

4

Unicode6.1定义范围:0~10 FFFF

0020 0000 ~

03FF FFFF

22~26

1111 10XX

10XX XXXX

10XX XXXX

10XX XXXX

10XX XXXX

5

0400 0000 ~

7FFF FFFF

27~31

1111 110X

10XX XXXX

10XX XXXX

10XX XXXX

10XX XXXX

10XX XXXX

6

表一,UCS-2到UTF-8的编码方式表

        例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx10xxxxxx 10xxxxxx。将6C49写成二进制是:011011000100 1001,用这个比特流依次代替模板中的x,得到:111001101011000110001001,即E6 B1 89。 
        读者可以用记事本测试一下我们的编码是否正确。 
        UTF-16以16位为单元对UCS进行编码。对于小于0×10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0×10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0×10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。 

        看到这里,读者要问了,对于汉字来说,使用UTF-8来说,存储的字节数"E6 B189"要比直接使用Unicode编码"6C49"还多啊。没办法,对于汉字来说,确实增多了。但对于英语系国家来说,UTF-8比Unicode省了。谁叫计算机是别们发明的呢,总是有点特权的。

9. Little endian和Big endian

        上一节已经提到,Unicode码可以采用UCS-2格式直接存储。以汉字”严“为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。 
        这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。 因此,第一个字节在前,就是”大头方式“(Big endian),第二个字节在前就是”小头方式“(Little endian)。 那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码? 
        Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格“(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。 
        如果一个文本文件的头两个字节是FEFF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。 

2. Poco中字符编码

2.1 编码的介绍

        有了上面的基本概念,对于Poco中的字符编码,理解就简单了。在Poco中存在ASCII,Latin1,Latin9,Windows1252,UTF16,UTF8编码。其中ASCII对应的字符集大小为128;Latin1和Latin9表达的对象为拉丁语,其对应的字符集大小为256;Windows1252对应的字符集大小也为256;UTF16,UTF8表达的字符集对象为UCS2,大小为2^32。下面把涉及的这几种编码说的详细一点:

       Latin1:Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。

  ISO-8859-1
  ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。
  ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在ISO-8859-1当中。
  因为ISO-8859-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin1就是利用了这个特性。ASCII编码是一个7位的容器,ISO-8859-1编码是一个8位的容器。
       Latin9:

       看代码的话,和Latin1的区别在于,Latin1用2个字节去表示文字符号,而Latin9用4个字节表示文字符号。

       UTF8:

       UTF-8是UNICODE的一种变长字符编码又称万国码,由KenThompson于1992年创建。现在已经标准化为RFC 3629。UTF-8用1到6个字节编码UNICODE字符。用在网页上可以同一页面显示中文简体繁体及其它语言(如日文,韩文)。UTF-8编码的优点,UTF-8编码可以通过屏蔽位和移位操作快速读写。字符串比较时strcmp()和wcscmp()的返回结果相同,因此使排序变得更加容易。字节FF和FE在UTF-8编码中永远不会出现,因此他们可以用来表明UTF-16或UTF-32文本(见BOM) UTF-8 是字节顺序无关的。它的字节顺序在所有系统中都是一样的,因此它实际上并不需要BOM。UTF-8编码的缺点,无法从UNICODE字符数判断出UTF-8文本的字节数,因为UTF-8是一种变长编码它需要用2个字节编码那些用扩展ASCII字符集只需1个字节的字符 ISO Latin-1 是UNICODE的子集,但不是UTF-8的子集 8位字符的UTF-8编码会被email网关过滤,因为internet信息最初设计为7位ASCII码。因此产生了UTF-7编码。 UTF-8 在它的表示中使用值100xxxxx的几率超过50%, 而现存的实现如ISO 2022, 4873, 6429, 和8859系统,会把它错认为是C1 控制码。因此产生了UTF-7.5编码。

       UTF16:

       UTF-16是Unicode的其中一个使用方式。 UTF是 Unicode Translation Format,即把Unicode转做某种格式的意思。它定义于ISO/IEC 10646-1的附录Q,而RFC2781也定义了相似的做法。在Unicode基本多文种平面定义的字符(无论是拉丁字母、汉字或其他文字或符号),一律使用2字节储存。而在辅助平面定义的字符,会以代理对(surrogate pair)的形式,以两个2字节的值来储存。UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节 (2字节) 储存,但UTF-16却无法兼容于ASCII编码。c#中默认的就是UTF-16,所以在处理c#字符串的时候只能是byte,stream等方式去处理。

       UTF-32:
       UTF-32 (或 UCS-4)是一种将Unicode字符编码的协定,对每一个Unicode码位使用恰好32位元。其它的Unicode transformation formats则使用不定长度编码。因为UTF-32对每个字符都使用4字节,就空间而言,是非常没有效率的。特别地,非基本多文种平面的字符在大部分文件中通常很罕见,以致于它们通常被认为不存在占用空间大小的讨论,使得UTF-32通常会是其它编码的二到四倍。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。与UTF-8及UTF-16相比,它有点更容易遭截断。
       

2.2 字符原集和表示之间的转换类

       Poco中的编码类都从TextEncoding类继承。TextEncoding类的接口定义如下:

[cpp] view plaincopy

  1. class Foundation_API TextEncoding  

  2. {  

  3. public:  

  4.     typedef SharedPtr Ptr;  

  5.       

  6.     enum  

  7.     {  

  8.         MAX_SEQUENCE_LENGTH = 6 /// The maximum character byte sequence length supported.  

  9.     };  

  10.       

  11.     typedef int CharacterMap[256];  

  12.   

  13.   

  14.     virtual ~TextEncoding();  

  15.   

  16.     virtual const char* canonicalName() const = 0;  

  17.   

  18.     virtual bool isA(const std::string& encodingName) const = 0;  

  19.               

  20.                // ……..  

  21.           

  22.     virtual int convert(const unsigned char* bytes) const;  

  23.   

  24.     virtual int queryConvert(const unsigned char* bytes, int length) const;  

  25.   

  26.     virtual int sequenceLength(const unsigned char* bytes, int length) const;  

  27.   

  28.     virtual int convert(int ch, unsigned char* bytes, int length) const;  

  29.   

  30.   

  31.                // ….  

  32.           

  33. protected:  

  34.     static TextEncodingManager& manager();  

  35.         /// Returns the TextEncodingManager.  

  36. };  

       我们可以把要表示的字符集称为字符原集,如UCS2,UCS4,它规定了字符集中存在哪些字符,并把每一个字符和数字之间建立一一映射关系。由于字符原集是个有穷集合,一个int值(2^32)足以表示其定义。一个字符的原集在被应用到计算机中时,会存在多种表示方式,如UCS2可以表示为UTF8,UTF16,我们称为原集的表示。

      TextEncoding中下面两个函数,用来把”原集的表示“转换为原集字符(一个int值)。

[cpp] view plaincopy

  1. int convert(const unsigned char* bytes) const;  

  2. int queryConvert(const unsigned char* bytes, int length) const;  

       而下面这个函数则用来把原集字符(一个int值)转换成”原集的表示“。

[cpp] view plaincopy

  1. int convert(int ch, unsigned char* bytes, int length) const;  

      拿UTF8Encoding类来举例,其原集为UCS2,表示方法是UTF8。

      ”原集字符“转成”UTF8“表示,其函数实现如下:

[cpp] view plaincopy

  1. int UTF8Encoding::convert(int ch, unsigned char* bytes, int length) const  

  2. {  

  3.     if (ch <= 0x7F)  

  4.     {  

  5.         if (bytes && length >= 1)  

  6.             *bytes = (unsigned char) ch;  

  7.         return 1;  

  8.     }  

  9.     else if (ch <= 0x7FF)  

  10.     {  

  11.         if (bytes && length >= 2)  

  12.         {  

  13.             *bytes++ = (unsigned char) (((ch >> 6) & 0x1F) | 0xC0);  

  14.             *bytes   = (unsigned char) ((ch & 0x3F) | 0x80);  

  15.         }  

  16.         return 2;  

  17.     }  

  18.     else if (ch <= 0xFFFF)  

  19.     {  

  20.         if (bytes && length >= 3)  

  21.         {  

  22.             *bytes++ = (unsigned char) (((ch >> 12) & 0x0F) | 0xE0);  

  23.             *bytes++ = (unsigned char) (((ch >> 6) & 0x3F) | 0x80);  

  24.             *bytes   = (unsigned char) ((ch & 0x3F) | 0x80);  

  25.         }  

  26.         return 3;  

  27.     }  

  28.     else if (ch <= 0x10FFFF)  

  29.     {  

  30.         if (bytes && length >= 4)  

  31.         {  

  32.             *bytes++ = (unsigned char) (((ch >> 18) & 0x07) | 0xF0);  

  33.             *bytes++ = (unsigned char) (((ch >> 12) & 0x3F) | 0x80);  

  34.             *bytes++ = (unsigned char) (((ch >> 6) & 0x3F) | 0x80);  

  35.             *bytes   = (unsigned char) ((ch & 0x3F) | 0x80);  

  36.         }  

  37.         return 4;  

  38.     }  

  39.     else return 0;  

  40. }  

     ”UTF8“表示转成”原集字符“,其函数实现如下:

[cpp] view plaincopy

  1. int UTF8Encoding::convert(const unsigned char* bytes) const  

  2. {  

  3.     int n = _charMap[*bytes];  

  4.     int uc;  

  5.       

  6.     switch (n)  

  7.     {  

  8.     case -6:  

  9.     case -5:  

  10.     case -1:  

  11.         return -1;  

  12.     case -4:   

  13.     case -3:   

  14.     case -2:  

  15.         if (!isLegal(bytes, -n)) return -1;  

  16.         uc = *bytes & ((0x07 << (n + 4)) | 0x03);  

  17.         break;  

  18.     default:  

  19.         return n;  

  20.     }  

  21.   

  22.     while (n++ < -1)   

  23.     {     

  24.         uc <<= 6;  

  25.         uc |= (*++bytes & 0x3F);  

  26.     }  

  27.     return uc;  

  28. }  

     这两段代码就是上面的表一《UCS-2到UTF-8的编码方式表》的代码表现。其他的编码也类似。在UTF16Encoding类中,由于UCS2和UTF16表示是一致的,所以不存在转换关系,但有bigendian和litterendian实现问题。在ASCIIEncoding类中,原集和其表现也一致,所以也不存在转换问题。

     下面是  TextEncoding和其相关类的类图:

     TextEncodingManager是TextEncoding类的工厂类,创建了ASCIIEncoding、UTF16Encoding等编码对象。

2.3 字符集之间的转换

     不同字符集之间的转换,实际上是不同字符原集的不同表示之间的转换。如果两个表示方法的原集相同,转换起来自然方便一些。Poco中提供了UnicodeConverter类用于UTF8和UTF16之间的转换。其定义如下:

[cpp] view plaincopy

  1. class Foundation_API UnicodeConverter  

  2. {  

  3. public:  

  4.     static void toUTF16(const std::string& utf8String, std::wstring& utf16String);  

  5.         /// Converts the given UTF-8 encoded string into an UTF-16 encoded wstring.  

  6.   

  7.     static void toUTF16(const char* utf8String, int length, std::wstring& utf16String);   

  8.         /// Converts the given UTF-8 encoded character sequence into an UTF-16 encoded string.  

  9.   

  10.     static void toUTF16(const char* utf8String, std::wstring& utf16String);   

  11.         /// Converts the given zero-terminated UTF-8 encoded character sequence into an UTF-16 encoded wstring.  

  12.   

  13.     static void toUTF8(const std::wstring& utf16String, std::string& utf8String);  

  14.         /// Converts the given UTF-16 encoded wstring into an UTF-8 encoded string.  

  15.   

  16.     static void toUTF8(const wchar_t* utf16String, int length, std::string& utf8String);  

  17.         /// Converts the given zero-terminated UTF-16 encoded wide character sequence into an UTF-8 encoded wstring.  

  18.   

  19.     static void toUTF8(const wchar_t* utf16String, std::string& utf8String);  

  20.         /// Converts the given UTF-16 encoded zero terminated character sequence into an UTF-8 encoded string.  

  21. };  

     注意UTF-16用在C++中是用wtring存储的。虽然UTF-16对应着UCS2,内部存储时,一个short已经足够。但在Linux下默认是占4个字节,当然在用GCC编译时可以使用-fshort-wchar来强制使用2个字节,而在Windows上被定义为unsigned short。

     如果两个表示方法的原集不同,则要考虑转换方向问题。比如说中文字符在ASCII码中不存在,那么毫无疑问,把中文字符转换成ASCII码自然无意义,这个方向的转换注定要失败。在Poco中,上述字符集之间的转换是用类TextConverter来实现的。下面是它的定义:

[cpp] view plaincopy

  1. class Foundation_API TextConverter  

  2.     /// A TextConverter converts strings from one encoding  

  3.     /// into another.  

  4. {  

  5. public:  

  6.     typedef int (*Transform)(int);  

  7.         /// Transform function for convert.  

  8.           

  9.     TextConverter(const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?');  

  10.         /// Creates the TextConverter. The encoding objects must not be deleted while the  

  11.         /// TextConverter is in use.  

  12.   

  13.     ~TextConverter();  

  14.         /// Destroys the TextConverter.  

  15.           

  16.     int convert(const std::string& source, std::string& destination, Transform trans);  

  17.         /// Converts the source string from inEncoding to outEncoding  

  18.         /// and appends the result to destination. Every character is  

  19.         /// passed to the transform function.  

  20.         /// If a character cannot be represented in outEncoding, defaultChar  

  21.         /// is used instead.  

  22.         /// Returns the number of encoding errors (invalid byte sequences  

  23.         /// in source).  

  24.   

  25.     int convert(const void* source, int length, std::string& destination, Transform trans);  

  26.         /// Converts the source buffer from inEncoding to outEncoding  

  27.         /// and appends the result to destination. Every character is  

  28.         /// passed to the transform function.  

  29.         /// If a character cannot be represented in outEncoding, defaultChar  

  30.         /// is used instead.  

  31.         /// Returns the number of encoding errors (invalid byte sequences  

  32.         /// in source).  

  33.   

  34.     int convert(const std::string& source, std::string& destination);  

  35.         /// Converts the source string from inEncoding to outEncoding  

  36.         /// and appends the result to destination.  

  37.         /// If a character cannot be represented in outEncoding, defaultChar  

  38.         /// is used instead.  

  39.         /// Returns the number of encoding errors (invalid byte sequences  

  40.         /// in source).  

  41.   

  42.     int convert(const void* source, int length, std::string& destination);  

  43.         /// Converts the source buffer from inEncoding to outEncoding  

  44.         /// and appends the result to destination.  

  45.         /// If a character cannot be represented in outEncoding, defaultChar  

  46.         /// is used instead.  

  47.         /// Returns the number of encoding errors (invalid byte sequences  

  48.         /// in source).  

  49.   

  50. private:  

  51.     TextConverter();  

  52.     TextConverter(const TextConverter&);  

  53.     TextConverter& operator = (const TextConverter&);  

  54.   

  55.     const TextEncoding& _inEncoding;  

  56.     const TextEncoding& _outEncoding;  

  57.     int                 _defaultChar;  

  58. };  

       如果要在流输出之前,进行字符集转换,Poco还提供了类StreamConverterBuf。其定义为:

[cpp] view plaincopy

  1. class Foundation_API StreamConverterBuf: public UnbufferedStreamBuf  

  2.     /// A StreamConverter converts streams from one encoding (inEncoding)  

  3.     /// into another (outEncoding).  

  4.     /// If a character cannot be represented in outEncoding, defaultChar  

  5.     /// is used instead.  

  6.     /// If a byte sequence is not valid in inEncoding, defaultChar is used  

  7.     /// instead and the encoding error count is incremented.  

  8. {  

  9. public:  

  10.     StreamConverterBuf(std::istream& istr, const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?');  

  11.         /// Creates the StreamConverterBuf and connects it  

  12.         /// to the given input stream.  

  13.   

  14.     StreamConverterBuf(std::ostream& ostr, const TextEncoding& inEncoding, const TextEncoding& outEncoding, int defaultChar = '?');  

  15.         /// Creates the StreamConverterBuf and connects it  

  16.         /// to the given output stream.  

  17.   

  18.     ~StreamConverterBuf();  

  19.         /// Destroys the StreamConverterBuf.  

  20.   

  21.     int errors() const;  

  22.         /// Returns the number of encoding errors encountered.  

  23.   

  24. protected:  

  25.     int readFromDevice();  

  26.     int writeToDevice(char c);  

  27.   

  28. private:  

  29.     std::istream*       _pIstr;  

  30.     std::ostream*       _pOstr;  

  31.     const TextEncoding& _inEncoding;  

  32.     const TextEncoding& _outEncoding;  

  33.     int                 _defaultChar;  

  34.     unsigned char       _buffer[TextEncoding::MAX_SEQUENCE_LENGTH];  

  35.     int                 _sequenceLength;  

  36.     int                 _pos;  

  37.     int                 _errors;  

  38. };  

2.4 迭代子

     TextBufferIterator和TextIterator实现了对流和字符串进行迭代。其使用大致如下:

[cpp] view plaincopy

  1. UTF8Encoding utf8Encoding;  

  2. char buffer[] = "…";  

  3. TextBufferIterator it(buffer, utf8Encoding);  

  4. TextBufferIterator end(it.end());  

  5. int n = 0;  

  6. while (it != end) { ++n; ++it; }  

      或:

[cpp] view plaincopy

  1. UTF8Encoding utf8Encoding;  

  2. std::string utf8String("….");  

  3. TextIterator it(utf8String, utf8Encoding);  

  4. TextIterator end(utf8String);  

  5. int n = 0;  

  6. while (it != end) { ++n; ++it; }  

      下面是一个完整的例子:

[cpp] view plaincopy

  1. #include "Poco/TextIterator.h"  

  2. #include "Poco/UTF8Encoding.h"  

  3. using Poco::TextIterator;  

  4. using Poco::UTF8Encoding;  

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

  6. {  

  7.     std::string utf8String("This is UTF-8 encoded text.");  

  8.     UTF8Encoding utf8;  

  9.     TextIterator it(utf8String, utf8);  

  10.     TextIterator end(utf8String);  

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

  12.     {  

  13.         int unicode = *it;  

  14.     }  

  15.     return 0;  

  16. }  

2.5 其他

      关于编码的其他类还包括了类UTF8和类Unicode。类UTF8实现了UTF8的字符大小转换和比较,当然中文是没有大小的,大小转换只是指英文字符。而类Unicode则可以判断字符是否是Unicode原集中定义的数字,字母等。

2.6 例子

[cpp] view plaincopy

  1. // TextTest.cpp : Defines the entry point for the console application.  

  2. //  

  3.   

  4. #include "stdafx.h"  

  5. #include "Poco/TextConverter.h"  

  6. #include "Poco/Latin1Encoding.h"  

  7. #include "Poco/UTF8Encoding.h"  

  8. #include "Poco/UTF16Encoding.h"  

  9. #include "Poco/UTF8String.h"  

  10. #include "Poco/TextIterator.h"  

  11. #include "Poco/UTF8Encoding.h"  

  12. #include   

  13. #include   

  14.   

  15. using Poco::TextConverter;  

  16. using Poco::Latin1Encoding;  

  17. using Poco::UTF8Encoding;  

  18. using Poco::UTF16Encoding;  

  19. using Poco::UTF8;  

  20. using Poco::TextIterator;  

  21. using Poco::UTF8Encoding;  

  22.   

  23. #include "Poco/StreamConverter.h"  

  24.   

  25. using Poco::OutputStreamConverter;  

  26.   

  27. void TestConvert()  

  28. {  

  29.     std::string latin1String("This is Latin-1 encoded text.");  

  30.     std::string utf8String;  

  31.     Latin1Encoding latin1;  

  32.     UTF8Encoding utf8;  

  33.     TextConverter converter(latin1, utf8);  

  34.     converter.convert(latin1String, utf8String);  

  35.     std::cout << utf8String << std::endl;  

  36.   

  37.     std::string latin1StringZ("中国.");  

  38.     std::string utf8StringZ;  

  39.     UTF16Encoding utf16;  

  40.     UTF8Encoding utf8Z;  

  41.     TextConverter converterZ(utf16, utf8Z);  

  42.     converterZ.convert(latin1StringZ, utf8StringZ);  

  43.     std::cout << utf8StringZ << std::endl;  

  44. }  

  45.   

  46. void TestStream()  

  47. {  

  48.     std::string latin1String("This is Latin-1 encoded text.");  

  49.     Latin1Encoding latin1;  

  50.     UTF8Encoding utf8;  

  51.     OutputStreamConverter converter(std::cout, latin1, utf8);  

  52.     converter << latin1String << std::endl; // console output will be UTF-8  

  53. }  

  54.   

  55. void TestUTF8()  

  56. {  

  57.     std::string s3("\303\274\303\266\303\244"); // "u"o"a  

  58.     UTF8::toUpperInPlace(s3);     

  59.     assert (s3 == "\303\234\303\226\303\204"); // "U"O"A  

  60.     UTF8::toLowerInPlace(s3);  

  61.     assert (s3 == "\303\274\303\266\303\244"); // "u"o"a  

  62. }  

  63.   

  64. void TestIterator()  

  65. {  

  66.     std::string utf8String("This is UTF-8 encoded text.");  

  67.     UTF8Encoding utf8;  

  68.     TextIterator it(utf8String, utf8);  

  69.     TextIterator end(utf8String);  

  70.     int unicode;  

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

  72.     {  

  73.         unicode = *it;  

  74.     }  

  75. }  

  76.   

  77. int _tmain(int argc, _TCHAR* argv[])  

  78. {  

  79.     TestLatinToUtf8();  

  80.     TestStream();  

  81.     TestUTF8();  

  82.     TestIterator();  

  83.     return 0;  

  84. }  

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

         在写程序的时候,有时候需要收集一些系统信息,用作软硬件的绑定或生成唯一的注册码信息等。Poco中提供了一个很简单的类Environment来实现这个功能。这个类的定义如下:

  1. class Foundation_API Environment  

  2.     /// This class provides access to environment variables  

  3.     /// and some general system information.  

  4. {  

  5. public:  

  6.     typedef UInt8 NodeId[6]; /// Ethernet address.  

  7.       

  8.     static std::string get(const std::string& name);  

  9.         /// Returns the value of the environment variable  

  10.         /// with the given name. Throws a NotFoundException  

  11.         /// if the variable does not exist.  

  12.           

  13.     static std::string get(const std::string& name, const std::string& defaultValue);  

  14.         /// Returns the value of the environment variable  

  15.         /// with the given name. If the environment variable  

  16.         /// is undefined, returns defaultValue instead.  

  17.           

  18.     static bool has(const std::string& name);  

  19.         /// Returns true iff an environment variable  

  20.         /// with the given name is defined.  

  21.           

  22.     static void set(const std::string& name, const std::string& value);  

  23.         /// Sets the environment variable with the given name  

  24.         /// to the given value.  

  25.   

  26.     static std::string osName();  

  27.         /// Returns the operating system name.  

  28.           

  29.     static std::string osVersion();  

  30.         /// Returns the operating system version.  

  31.           

  32.     static std::string osArchitecture();  

  33.         /// Returns the operating system architecture.  

  34.           

  35.     static std::string nodeName();  

  36.         /// Returns the node (or host) name.  

  37.           

  38.     static void nodeId(NodeId& id);  

  39.         /// Returns the Ethernet address of the first Ethernet  

  40.         /// adapter found on the system.  

  41.         ///  

  42.         /// Throws a SystemException if no Ethernet adapter is available.  

  43.           

  44.     static std::string nodeId();  

  45.         /// Returns the Ethernet address (format "xx:xx:xx:xx:xx:xx")  

  46.         /// of the first Ethernet adapter found on the system.  

  47.         ///  

  48.         /// Throws a SystemException if no Ethernet adapter is available.  

  49.           

  50.     static unsigned processorCount();  

  51.         /// Returns the number of processors installed in the system.  

  52.         ///  

  53.         /// If the number of processors cannot be determined, returns 1.  

  54.           

  55.     static Poco::UInt32 libraryVersion();  

  56.         /// Returns the POCO C++ Libraries version as a hexadecimal  

  57.         /// number in format 0xAABBCCDD, where  

  58.         ///    - AA is the major version number,  

  59.         ///    - BB is the minor version number,  

  60.         ///    - CC is the revision number, and  

  61.         ///    - DD is the patch level number.  

  62.         ///  

  63.         /// Some patch level ranges have special meanings:  

  64.         ///    - Dx mark development releases,  

  65.         ///    - Ax mark alpha releases, and  

  66.         ///    - Bx mark beta releases.  

  67. };  

          从定义中我们可以看到,它的功能包括:

         1.  获取系统第一块网卡的信息

         2.  获取、设置指定名称的环境变量值

         3.  获取操作系统名称、版本、结构

         4.  获取处理器数量

         下面是其的一个使用例子:

[cpp] view plaincopy

  1. #include "stdafx.h"  

  2. #include "Poco/Environment.h"  

  3. #include   

  4. using Poco::Environment;  

  5.   

  6.   

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

  8. {  

  9.     std::cout  

  10.         << "OS Name: " << Environment::osName() << std::endl  

  11.         << "OS Version: " << Environment::osVersion() << std::endl  

  12.         << "OS Arch: " << Environment::osArchitecture() << std::endl  

  13.         << "Node Name: " << Environment::nodeName() << std::endl  

  14.         << "Node ID: " << Environment::nodeId() << std::endl  

  15.         << "Processor Count: " << Environment::processorCount() << std::endl  

  16.         << "Library Version: " << Environment::libraryVersion() << std::endl;  

  17.   

  18.     if (Environment::has("TEMP"))  

  19.         std::cout << "TEMP: " << Environment::get("TEMP") << std::endl;  

  20.     Environment::set("POCO", "foo");  

  21.   

  22.     return 0;  

  23.   

  24. }  

         Environment的内部的实现上很简单,依赖于EnvironmentImpl类,每中操作系统实现了自己的EnvironmentImpl类,从而实现了对不同操作系统统一接口。

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

        在Poco库中,与时间和日期相关的一些类,其内部实现是非常简单的。看相关文档时,比较有意思的倒是历史上的不同时间表示法。

1. 系统时间函数

        在编程时,时间函数不可避免的会被使用。linux系统下相关时间的数据结构有time_t,timeval,timespec,tm,clock_t; windows下time_t,tm,SYSTEMTIME,FILETIME,clock_t。其中clock_t、timeval、timespec用于表示时间跨度,time_t、tm、SYSTEMTIME,FILETIME用于表示绝对时间。不同的数据结构之间,多少也有些差异。

        首先这些时间结构体的精度不同,Second(time_t/tm), microsecond(timeval/SYSTEMTIME),  100nanoSeconds(FILETIME),nanoSeconds(timespec)。还有一些结构和操作系统相关,如clock_t,windows下为1毫秒,POSIX 下为1微秒,对应clock_t,不同平台的差异,可以用宏CLOCKS_PER_SEC解决。

        起始时间不同,time_t起始于1970年1月1日0时0分0秒,tm表示起始于1900年,SYSTEMTIME/FILETIME起始于1601年,clock起始于机器开机。

        上面各个时间的定义如下:

[cpp] view plaincopy

  1. typedef struct _FILETIME {    

  2.                           DWORD dwLowDateTime;    

  3.                           DWORD dwHighDateTime;    

  4. } FILETIME, *PFILETIME;    

  5.   

  6. typedef struct _SYSTEMTIME  

  7. {  

  8.         WORD wYear;  

  9.         WORD wMonth;  

  10.         WORD wDayOfWeek;  

  11.         WORD wDay;  

  12.         WORD wHour;  

  13.         WORD wMinute;  

  14.         WORD wSecond;  

  15.         WORD wMilliseconds; // 毫秒  

  16. } SYSTEMTIME, *PSYSTEMTIME;  

  17.   

  18. struct tm  

  19. {  

  20.         int tm_sec;     /* 秒 – 取值区间为[0,59] */  

  21.         int tm_min;     /* 分 - 取值区间为[0,59] */  

  22.         int tm_hour;    /* 时 - 取值区间为[0,23] */  

  23.         int tm_mday;    /* 一个月中的日期 - 取值区间为[1,31] */  

  24.         int tm_mon;     /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */  

  25.         int tm_year;    /* 年份,其值等于实际年份减去1900 */  

  26.         int tm_wday;    /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */  

  27.         int tm_yday;    /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */  

  28.         int tm_isdst;   /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/  

  29. };  

  30.   

  31.   

  32. typedef __time64_t time_t;  // 秒  

  33.   

  34.   

  35. struct timeval  

  36. {  

  37.         long    tv_sec;         /* seconds */  

  38.         long    tv_usec;        /* and microseconds 毫秒*/  

  39. };  

  40.   

  41.   

  42. struct timespec  

  43. {  

  44.         __time_t tv_sec;  /*seconds 秒*/  

  45.         long int tv_nsec; /*nanoseconds 纳秒*/  

  46. }  

  47.   

  48. typedef unsigned long clock_t;  // 毫秒  

        同这些数据结构相关联,C语言为tm,time_t提供了一组函数用于时间运算和数据结构转换:

[cpp] view plaincopy

  1. // 日历时间(一个用time_t表示的整数)  

  2.   

  3. // 比较日历时间  

  4. double difftime(time_t time1, time_t time0);  

  5. // 获取日历时间  

  6. time_t time(time_t * timer);  

  7. // 转换日历时间为字符串  

  8. char * ctime(const time_t *timer);  

  9. // 转换日历时间为我们平时看到的把年月日时分秒分开显示的时间格式tm(GMT timezone)  

  10. struct tm * gmtime(const time_t *timer);        

  11. // 转换日历时间为我们平时看到的把年月日时分秒分开显示的时间格式tm(本地 timezone)  

  12. struct tm * localtime(const time_t * timer);  

  13. // 关于本地时间的计算公式:  

  14. localtime = utctime[Gmt time] + utcOffset()[时区偏移] + dst()[夏令时偏移]  

  15.   

  16.   

  17. // 把tm转换为字符串  

  18. char * asctime(const struct tm * timeptr);  

  19. // 把tm转换为日历时间  

  20. time_t mktime(struct tm * timeptr);  

  21.   

  22.   

  23. // 获取开机以来的微秒数  

  24. clock_t clock (void);  

       Windows下特有的时间转换函数包括:

       GetLocalTime能够得到本地电脑设置时区的时间,得到的类型是SYSTEMTIME的类型。

[cpp] view plaincopy

  1. void GetSystemTime(LPSYSTEMTIME lpSystemTime);          // GetSystemTime函数获得当前的UTC时间  

  2. void GetLocalTime(LPSYSTEMTIME lpSystemTime);           // GetLocalTime获得当前的本地时间  

  3.   

  4. BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime,    

  5.                           LPFILETIME lpFileTime);    

  6. BOOL FileTimeToSystemTime(const FILETIME* lpFileTime,    

  7.                           LPSYSTEMTIME lpSystemTime);    

  8. BOOL LocalFileTimeToFileTime(const FILETIME* lpLocalFileTime,    

  9.                              LPFILETIME lpFileTime);    

  10. BOOL FileTimeToLocalFileTime(const FILETIME* lpFileTime,    

  11.                              LPFILETIME lpLocalFileTime);    

      Windows下特有的获取时间精度的函数包括(精度微秒):

[cpp] view plaincopy

  1. BOOL  QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);  

  2. BOOL  QueryPerformanceCounter(LARGE_INTEGER *lpCount);  

        我们回想一下程序中的时间数据结构和函数的用法,可以发现主要是2个目的:
        1. 获取绝对时间
        2. 获取两个时间点的相对时间

2. Timestamp类

        同C语言中函数类似,Poco中定义了自己的时间类。Timestamp类似于time_t,用于获取比较日历时间。Timestamp定义如下:

[cpp] view plaincopy

  1. class Foundation_API Timestamp  

  2. {  

  3. public:  

  4.     typedef Int64 TimeVal;    /// monotonic UTC time value in microsecond resolution  

  5.     typedef Int64 UtcTimeVal; /// monotonic UTC time value in 100 nanosecond resolution  

  6.     typedef Int64 TimeDiff;   /// difference between two timestamps in microseconds  

  7.   

  8.     Timestamp();  

  9.         /// Creates a timestamp with the current time.  

  10.           

  11.     Timestamp(TimeVal tv);  

  12.         /// Creates a timestamp from the given time value.  

  13.           

  14.     Timestamp(const Timestamp& other);  

  15.         /// Copy constructor.  

  16.           

  17.     ~Timestamp();  

  18.         /// Destroys the timestamp  

  19.           

  20.     Timestamp& operator = (const Timestamp& other);  

  21.     Timestamp& operator = (TimeVal tv);  

  22.       

  23.     void swap(Timestamp& timestamp);  

  24.         /// Swaps the Timestamp with another one.  

  25.       

  26.     void update();  

  27.         /// Updates the Timestamp with the current time.  

  28.   

  29.   

  30.     bool operator == (const Timestamp& ts) const;  

  31.     bool operator != (const Timestamp& ts) const;  

  32.     bool operator >  (const Timestamp& ts) const;  

  33.     bool operator >= (const Timestamp& ts) const;  

  34.     bool operator <  (const Timestamp& ts) const;  

  35.     bool operator <= (const Timestamp& ts) const;  

  36.       

  37.     Timestamp  operator +  (TimeDiff d) const;  

  38.     Timestamp  operator -  (TimeDiff d) const;  

  39.     TimeDiff   operator -  (const Timestamp& ts) const;  

  40.     Timestamp& operator += (TimeDiff d);  

  41.     Timestamp& operator -= (TimeDiff d);  

  42.       

  43.     std::time_t epochTime() const;  

  44.         /// Returns the timestamp expressed in time_t.  

  45.         /// time_t base time is midnight, January 1, 1970.  

  46.         /// Resolution is one second.  

  47.           

  48.     UtcTimeVal utcTime() const;  

  49.         /// Returns the timestamp expressed in UTC-based  

  50.         /// time. UTC base time is midnight, October 15, 1582.  

  51.         /// Resolution is 100 nanoseconds.  

  52.       

  53.     TimeVal epochMicroseconds() const;  

  54.         /// Returns the timestamp expressed in microseconds  

  55.         /// since the Unix epoch, midnight, January 1, 1970.  

  56.       

  57.     TimeDiff elapsed() const;  

  58.         /// Returns the time elapsed since the time denoted by  

  59.         /// the timestamp. Equivalent to Timestamp() - *this.  

  60.       

  61.     bool isElapsed(TimeDiff interval) const;  

  62.         /// Returns true iff the given interval has passed  

  63.         /// since the time denoted by the timestamp.  

  64.       

  65.     static Timestamp fromEpochTime(std::time_t t);  

  66.         /// Creates a timestamp from a std::time_t.  

  67.           

  68.     static Timestamp fromUtcTime(UtcTimeVal val);  

  69.         /// Creates a timestamp from a UTC time value.  

  70.           

  71.     static TimeVal resolution();  

  72.         /// Returns the resolution in units per second.  

  73.         /// Since the timestamp has microsecond resolution,  

  74.         /// the returned value is always 1000000.  

  75.   

  76. private:  

  77.     TimeVal _ts;  

  78. };  

        Timestamp内部定义了一个Int64的变量_ts。存储了一个基于utc时间的64位int值,理论上可以提供微秒级的精度(实际精度依赖于操作系统)。由于Poco::Timestamp是基于UTC(世界标准时间或世界協調時間)的,所以它是独立于时区设置的。Poco::Timestamp实现了值语义,比较和简单的算术操作。
        1. UTC(Coordinated Universal Time)是从1582年10月15日深夜开始计时的. Poco库中精度为100纳秒。
        2. epochtime指是从1970年1月1日深夜开始计时的(指unix诞生元年)。Poco库中精度为1秒。

数据类型:
        Poco::Timestamp内部定义了下列数据类型:
        1. TimeVal
          一个64位的int整数值,保存utc时间,精度微秒
        2. UtcTimeVal
         一个64位的int整数值,保存utc时间,精度100纳秒(真实精度仍然是微秒)
        3. TimeDiff
        一个64位的int整数值,保存两个Timestamp的差值,精度微秒

构造函数:
        1. 默认构造函数会以当前时间初始化一个Timestamp值,基于UTC时间(从1582年10月15日开始计时,精度为100纳秒)
        2. 提供了两个静态函数用于创建Timestamp对象,
                  a) Timestamp fromEpochTime(time_ttime)。这个函数从time_t构建,内部会把EpochTime(从1970年1月1日深夜开始计时的,精度为1秒)的时间转换成为UTC时间。
                  b) Timestamp fromUtcTime(UtcTimeVal val)。这个函数从一个UtcTimeVal构建。

Timestamp的成员函数:
        1. time_t epochTime() const
        返回一个以epoch time计算的日历时间(精度秒)。(函数内部会把基于UTC时间的值转为基于epoch time的值)
        2. UtcTimeVal utcTime() const
        返回一个以UTC时间计算的日历时间(精度100纳秒)。
        3. TimeVal epochMicroseconds() const
        返回一个以epoch time计算的日历时间(精度微秒)
        4. void update()
        取当前的时间更新
        5. TimeDiff elapsed() const
        返回当前时间与Timestamp内部时间_ts的一个时间差值(精度微秒)
        6. bool isElapsed(TimeDiff interval) const
        如果当前时间与Timestamp内部时间_ts的一个时间差值大于interval时间,返回true。(精度微秒)

Timestamp算术计算:
        1. Timestamp operator + (TimeDiff diff) const
        增加一个时间偏移,并返回值。(精度微秒)
        2. Timestamp operator - (TimeDiff diff) const
        减掉一个时间偏移,并返回值。(精度微秒)
        3. TimeDiff operator - (const Timestamp&ts) const
        返回两个Timestamp对象的时间偏移。(精度微秒)
        4. Timestamp& operator += (TimeDiff d)
            Timestamp& operator -=(TimeDiff d)
        增加或减小一个时间偏移值

        下面来看一个例子:

[cpp] view plaincopy

  1. #include "Poco/Timestamp.h"  

  2. #include   

  3. using Poco::Timestamp;  

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

  5. {  

  6.     Timestamp now; // the current date and time  

  7.     std::time_t t1 = now.epochTime(); // convert to time_t …  

  8.     Timestamp ts1(Timestamp::fromEpochTime(t1)); // … and back again  

  9.     for (int i = 0; i < 100000; ++i) ; // wait a bit  

  10.     Timestamp::TimeDiff diff = now.elapsed(); // how long did it take?  

  11.     Timestamp start(now); // save start time  

  12.     now.update(); // update with current  

  13.     time diff = now - start; // again, how long?  

  14.     return 0;  

  15. }  

3. DateTime类

        Poco中提供了DateTime类,作用和tm类似。下面是它的定义:

[cpp] view plaincopy

  1. class Foundation_API DateTime  

  2. {  

  3. public:  

  4.     enum Months  

  5.         /// Symbolic names for month numbers (1 to 12).  

  6.     {  

  7.         JANUARY = 1,  

  8.         FEBRUARY,  

  9.         MARCH,  

  10.         APRIL,  

  11.         MAY,  

  12.         JUNE,  

  13.         JULY,  

  14.         AUGUST,  

  15.         SEPTEMBER,  

  16.         OCTOBER,  

  17.         NOVEMBER,  

  18.         DECEMBER  

  19.     };  

  20.       

  21.     enum DaysOfWeek  

  22.         /// Symbolic names for week day numbers (0 to 6).  

  23.     {  

  24.         SUNDAY = 0,  

  25.         MONDAY,  

  26.         TUESDAY,  

  27.         WEDNESDAY,  

  28.         THURSDAY,  

  29.         FRIDAY,  

  30.         SATURDAY  

  31.     };  

  32.           

  33.     DateTime();  

  34.         /// Creates a DateTime for the current date and time.  

  35.   

  36.   

  37.     DateTime(const Timestamp& timestamp);  

  38.         /// Creates a DateTime for the date and time given in  

  39.         /// a Timestamp.  

  40.           

  41.     DateTime(int year, int month, int day, int hour = 0, int minute = 0, int   

  42.   

  43.   

  44. second = 0, int millisecond = 0, int microsecond = 0);  

  45.         /// Creates a DateTime for the given Gregorian date and time.  

  46.         ///   * year is from 0 to 9999.  

  47.         ///   * month is from 1 to 12.  

  48.         ///   * day is from 1 to 31.  

  49.         ///   * hour is from 0 to 23.  

  50.         ///   * minute is from 0 to 59.  

  51.         ///   * second is from 0 to 59.  

  52.         ///   * millisecond is from 0 to 999.  

  53.         ///   * microsecond is from 0 to 999.  

  54.   

  55.   

  56.     DateTime(double julianDay);  

  57.         /// Creates a DateTime for the given Julian day.  

  58.   

  59.   

  60.     DateTime(Timestamp::UtcTimeVal utcTime, Timestamp::TimeDiff diff);  

  61.         /// Creates a DateTime from an UtcTimeVal and a TimeDiff.  

  62.         ///  

  63.         /// Mainly used internally by DateTime and friends.  

  64.   

  65.   

  66.     DateTime(const DateTime& dateTime);  

  67.         /// Copy constructor. Creates the DateTime from another one.  

  68.   

  69.   

  70.     ~DateTime();  

  71.         /// Destroys the DateTime.  

  72.   

  73.   

  74.     DateTime& operator = (const DateTime& dateTime);  

  75.         /// Assigns another DateTime.  

  76.           

  77.     DateTime& operator = (const Timestamp& timestamp);  

  78.         /// Assigns a Timestamp.  

  79.   

  80.   

  81.     DateTime& operator = (double julianDay);  

  82.         /// Assigns a Julian day.  

  83.   

  84.   

  85.     DateTime& assign(int year, int month, int day, int hour = 0, int minute = 0,   

  86.   

  87.   

  88. int second = 0, int millisecond = 0, int microseconds = 0);  

  89.         /// Assigns a Gregorian date and time.  

  90.         ///   * year is from 0 to 9999.  

  91.         ///   * month is from 1 to 12.  

  92.         ///   * day is from 1 to 31.  

  93.         ///   * hour is from 0 to 23.  

  94.         ///   * minute is from 0 to 59.  

  95.         ///   * second is from 0 to 59.  

  96.         ///   * millisecond is from 0 to 999.  

  97.         ///   * microsecond is from 0 to 999.  

  98.   

  99.   

100.     void swap(DateTime& dateTime);  

101.         /// Swaps the DateTime with another one.  

102.   

103.   

104.     int year() const;  

105.         /// Returns the year.  

106.           

107.     int month() const;  

108.         /// Returns the month (1 to 12).  

109.       

110.     int week(int firstDayOfWeek = MONDAY) const;  

111.         /// Returns the week number within the year.  

112.         /// FirstDayOfWeek should be either SUNDAY (0) or MONDAY (1).  

113.         /// The returned week number will be from 0 to 53. Week number 1 is   

114.   

115.   

116. the week   

117.         /// containing January 4. This is in accordance to ISO 8601.  

118.         ///   

119.         /// The following example assumes that firstDayOfWeek is MONDAY. For 2005, which started  

120.         /// on a Saturday, week 1 will be the week starting on Monday, January 3.  

121.         /// January 1 and 2 will fall within week 0 (or the last week of the previous year).  

122.         ///  

123.         /// For 2007, which starts on a Monday, week 1 will be the week   

124.   

125.   

126. startung on Monday, January 1.  

127.         /// There will be no week 0 in 2007.  

128.       

129.     int day() const;  

130.         /// Returns the day witin the month (1 to 31).  

131.           

132.     int dayOfWeek() const;  

133.         /// Returns the weekday (0 to 6, where  

134.         /// 0 = Sunday, 1 = Monday, …, 6 = Saturday).  

135.       

136.     int dayOfYear() const;  

137.         /// Returns the number of the day in the year.  

138.         /// January 1 is 1, February 1 is 32, etc.  

139.       

140.     int hour() const;  

141.         /// Returns the hour (0 to 23).  

142.           

143.     int hourAMPM() const;  

144.         /// Returns the hour (0 to 12).  

145.       

146.     bool isAM() const;  

147.         /// Returns true if hour < 12;  

148.   

149.   

150.     bool isPM() const;  

151.         /// Returns true if hour >= 12.  

152.           

153.     int minute() const;  

154.         /// Returns the minute (0 to 59).  

155.           

156.     int second() const;  

157.         /// Returns the second (0 to 59).  

158.           

159.     int millisecond() const;  

160.         /// Returns the millisecond (0 to 999)  

161.       

162.     int microsecond() const;  

163.         /// Returns the microsecond (0 to 999)  

164.       

165.     double julianDay() const;  

166.         /// Returns the julian day for the date and time.  

167.           

168.     Timestamp timestamp() const;  

169.         /// Returns the date and time expressed as a Timestamp.  

170.   

171.   

172.     Timestamp::UtcTimeVal utcTime() const;  

173.         /// Returns the date and time expressed in UTC-based  

174.         /// time. UTC base time is midnight, October 15, 1582.  

175.         /// Resolution is 100 nanoseconds.  

176.           

177.     bool operator == (const DateTime& dateTime) const;    

178.     bool operator != (const DateTime& dateTime) const;    

179.     bool operator <  (const DateTime& dateTime) const;     

180.     bool operator <= (const DateTime& dateTime) const;     

181.     bool operator >  (const DateTime& dateTime) const;     

182.     bool operator >= (const DateTime& dateTime) const;     

183.   

184.   

185.     DateTime  operator +  (const Timespan& span) const;  

186.     DateTime  operator -  (const Timespan& span) const;  

187.     Timespan  operator -  (const DateTime& dateTime) const;  

188.     DateTime& operator += (const Timespan& span);  

189.     DateTime& operator -= (const Timespan& span);  

190.       

191.     void makeUTC(int tzd);  

192.         /// Converts a local time into UTC, by applying the given time zone   

193.   

194.   

195. differential.  

196.           

197.     void makeLocal(int tzd);  

198.         /// Converts a UTC time into a local time, by applying the given time   

199.   

200.   

201. zone differential.  

202.       

203.     static bool isLeapYear(int year);  

204.         /// Returns true if the given year is a leap year;  

205.         /// false otherwise.  

206.           

207.     static int daysOfMonth(int year, int month);  

208.         /// Returns the number of days in the given month  

209.         /// and year. Month is from 1 to 12.  

210.           

211.     static bool isValid(int year, int month, int day, int hour = 0, int minute =   

212.   

213.   

214. 0, int second = 0, int millisecond = 0, int microsecond = 0);  

215.         /// Checks if the given date and time is valid  

216.         /// (all arguments are within a proper range).  

217.         ///  

218.         /// Returns true if all arguments are valid, false otherwise.  

219.           

220. protected:    

221.     // …  

222.   

223. private:  

224.     // …  

225.   

226.     Timestamp::UtcTimeVal _utcTime;  

227.     short  _year;  

228.     short  _month;  

229.     short  _day;  

230.     short  _hour;  

231.     short  _minute;  

232.     short  _second;  

233.     short  _millisecond;  

234.     short  _microsecond;  

235. };  

        Poco::DateTime是基于格里高利历(Gregorian calendar)(就是公历啦)设计的。它除了可以用来保存日历时间外,还可以被用于日期计算。如果只是为了日历时间的存储,Timestamp类更加适合。在DateTime的内部,DateTime类用两种格式维护了日期和时间。第一种是UTC日历时间。第二种是用年、月、日、时、分、秒、微秒、毫秒。为了进行内部时间日期之间的换算,DateTime使用了儒略日(Julianday)历法。

格里高利历(Gregoriancalendar)
        格里高利历就是我们通常讲的公历。它以耶稣的诞生为初始年份,也就是公元0001年。格里高利历以日为基本单位,1年有365或366天,分成12个月,每个月时长不等。由于不同国家采用格里高利历时间不同(德国1582,英国1752),所以格里高利历日期和旧式的日期有差别,即使是用来表示历史上相同的一件事。一个耶稣像,^_^。
      _      xxxx     _
     /_;-.__ / _\  _.-;_\
        `-._`'`_/'`.-'
            `\  /`
            |  /
            /-.(
            \_._\
            \ \`;
             > |/
            / //
            |//
            \(\
        

儒略日和儒略日日期 儒略日的起点订在公元前4713年(天文学上记为 -4712年)1月1日格林威治时间平午(世界时12:00),即JD 0指定为UT时间B.C.4713年1月1日12:00到UC时间B.C.4713年1月2日12:00的24小时。注意这一天是礼拜一。每一天赋予了一个唯一的数字,顺数而下,如:1996年1月1日12:00:00的儒略日是2450084。这个日期是考虑了太阳、月亮的轨道运行周期,以及当时收税的间隔而订出来的。Joseph Scliger定义儒略周期为7980年,是因28、19、15的最小公倍数为28×19×15=7980。

日期的注意事项: 1.0是一个合法数字(根据ISO 8691和天文年编号)
2. 0年是一个闰年。
3. 负数是不支持的。比如说公元前1年。
4. 格里高利历同历史上的日期可能不同,由于它们采用的日历方式不同。
5. 最好只是用DateTime用来计算当前的时间。对于历史上的或者天文日历的时间计算,还是使用其他的特定软件。

构造DateTime: 1.可以从一个已有的DateTime构造
2. 当前的时间和日期
3. 一个Timestamp对象
4. 用年、月、日、时、分、秒、微秒、毫秒构造
5. 使用一个儒略日日期构造(用double形式保存)

成员函数: 1.int year() const
  返回年
2. intmonth() const
    返回月(1-12)
3. intweek(int firstDayOfWeek = DateTime::MONDAY) const
    返回所在的周,根据ISO 8601标准(第一周是1月4日所在的周),一周的第一天是礼拜一或者礼拜天。
4. intday() const
    返回月中的所在天(1 - 31)
5. intdayOfWeek() const
    返回周中的所在天 0为周日,1为周一,…..
6. intdayOfYear() const
    返回年中的所在天(1 - 366)
7. inthour() const
    返回天中所在小时(0 - 23)
8. inthourAMPM() const
    返回上下午所在小时(0 - 12)
9.bool isAM() const
    如果上午返回真
10.bool isPM() const
    如果下午返回真
11.int minute() const
    返回分钟数(0 - 59)
12.int second() const
    返回秒数(0 - 59)
13.int millisecond() const
    返回毫秒数(0 - 999)
14.int microsecond() const
    返回微秒数(0 - 999)
15.Timestamp timestamp() const
    返回用Timestamp保存的日历时间(精度微秒)
16.Timestamp::UtcTimeVal utcTime() const
    返回用Timestamp保存的日历时间(精度100纳秒)
17.DateTime支持关系运算符(==, !=, >, >=, <, <=).
18.DateTime支持算术操作(+, -, +=, -=)

静态函数: 1.bool isLeapYear(int year)
    所给年是否闰年
2. intdaysOfMonth(int year, int month)
    所给年和月的天数
3.bool isValid(int year, int month, int day, int hour, int minute, int second,int millisecond, int microsecond)
    判断所给年月日是否合法

下面是DateTime的一个例子:

[cpp] view plaincopy

  1. #include "Poco/DateTime.h"  

  2. using Poco::DateTime;  

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

  4. {  

  5.     DateTime now; // the current date and time in UTC  

  6.     int year = now.year();  

  7.     int month = now.month();  

  8.     int day = now.day();  

  9.     int dow = now.dayOfWeek();  

  10.     int doy = now.dayOfYear();  

  11.     int hour = now.hour();  

  12.     int hour12 = now.hourAMPM();  

  13.     int min = now.minute();  

  14.     int sec = now.second();  

  15.     int ms = now.millisecond();  

  16.     int us = now.microsecond();  

  17.     double jd = now.julianDay();  

  18.     Poco::Timestamp ts = now.timestamp();  

  19.     DateTime xmas(2006, 12, 25); // 2006-12-25 00:00:00  

  20.     Poco::Timespan timeToXmas = xmas - now;  

  21.   

  22.   

  23.     DateTime dt(1973, 9, 12, 2, 30, 45); // 1973-09-12 02:30:45  

  24.     dt.assign(2006, 10, 13, 13, 45, 12, 345); // 2006-10-13 12:45:12.345  

  25.     bool isAM = dt.isAM(); // false  

  26.     bool isPM = dt.isPM(); // true  

  27.     bool isLeap = DateTime::isLeapYear(2006); // false  

  28.     int days = DateTime::daysOfMonth(2006, 2); // 28  

  29.     bool isValid = DateTime::isValid(2006, 02, 29); // false  

  30.     dt.assign(2006, DateTime::OCTOBER, 22); // 2006-10-22 00:00:00  

  31.     if (dt.dayOfWeek() == DateTime::SUNDAY)  

  32.     {  

  33.         // …  

  34.     }  

  35.     return 0;  

  36. }  

4. LocalDateTime类

Poco::LocalDateTime同Poco::DateTime类似,不同的是Poco::LocalDateTime存储一个本地时间。关于本地时间和UTC时间有如下计算公式:
(UTC时间 = 本地时间 - 时区差).

构造函数: 1. 通过当前时间构造
2. 通过Timestamp对象
3. 通过年、月、日、时、分、秒、微秒、毫秒构造
4. 通过儒略日时间构造(儒略日时间用double存储)
5. 作为可选项。时区可作为构造时第一个参数被指定。(如果没有指定的话,会使用系统当前的时区)

成员函数: 1. LocalDateTime支持所有的DateTime的函数。
2. 在进行比较之前,所有的关系操作符函数会把时间都换算为UTC时间
3. int tzd() const
   返回时区差
4. DateTime utc() const
   转换本地时间到utc时间

下面是一个例子:

[cpp] view plaincopy

  1. #include "Poco/LocalDateTime.h"  

  2. using Poco::LocalDateTime;  

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

  4. {  

  5.     LocalDateTime now; // the current date and local time  

  6.     int year = now.year();  

  7.     int month = now.month();  

  8.     int day = now.day();  

  9.     int dow = now.dayOfWeek();  

  10.     int doy = now.dayOfYear();  

  11.     int hour = now.hour();  

  12.     int hour12 = now.hourAMPM();  

  13.     int min = now.minute();  

  14.     int sec = now.second();  

  15.     int ms = now.millisecond();  

  16.     int us = now.microsecond();  

  17.     int tzd = now.tzd();  

  18.     double jd = now.julianDay();  

  19.     Poco::Timestamp ts = now.timestamp();  

  20.   

  21.   

  22.     LocalDateTime dt1(1973, 9, 12, 2, 30, 45); // 1973-09-12 02:30:45  

  23.     dt1.assign(2006, 10, 13, 13, 45, 12, 345); // 2006-10-13 12:45:12.345  

  24.     LocalDateTime dt2(3600, 1973, 9, 12, 2, 30, 45, 0, 0); // UTC +1 hour  

  25.     dt2.assign(3600, 2006, 10, 13, 13, 45, 12, 345, 0);  

  26.     Poco::Timestamp nowTS;  

  27.     LocalDateTime dt3(3600, nowTS); // construct from Timestamp  

  28.     return 0;  

  29. }  

5. Timespan类

Poco::Timespan能够提供一个微秒精度的时间间隔,也可以用天、小时、分钟、秒、微秒、毫秒来表示。在其内部这个时间间隔用一个64-bit整形来表示。

构造函数: 1. 一个TimeStamp::TimeDiff对象(微秒精度)
2. 秒+微秒
   主要用于从timeval结构体构建
3. 通过日、时、分、秒、微秒构造

操作符: 1. Poco::Timespan支持所有的关系操作符
(==, !=, <, <=, >, >=)
2. Poco::Timespan支持加法和减法操作
(+, -, +=, -=)

成员函数: 1. int days() const
   返回时间跨度的天
2. int hours() const
   返回时间跨度的小时(0 - 23)
3. int totalHours() const
   返回时间跨度总的小时数
4. int minutes() const
   返回时间跨度的分钟(0 - 59)
5. int totalMinutes() const
   返回时间跨度总的分钟数
6. int seconds() const
   返回时间跨度的秒(0 - 60)
7. int totalSeconds() const
   返回时间跨度总的秒数
8. int milliseconds() const
   返回时间跨度的毫秒((0 - 999)
9. int totalMilliseconds() const
   返回时间跨度总的毫秒数
10. int microseconds() const
   返回时间跨度的微秒( (0 - 999)
11. int totalMicroseconds() const
   返回时间跨度总的微秒数

下面是一个例子:

[cpp] view plaincopy

  1. #include "Poco/Timespan.h"  

  2. using Poco::Timespan;  

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

  4. {  

  5.     Timespan ts1(1, 11, 45, 22, 123433); // 1d 11h 45m 22.123433s  

  6.     Timespan ts2(33*Timespan::SECONDS); // 33s  

  7.     Timespan ts3(2*Timespan::DAYS + 33*Timespan::HOURS); // 3d 33h  

  8.     int days = ts1.days(); // 1  

  9.     int hours = ts1.hours(); // 11  

  10.     int totalHours = ts1.totalHours(); // 35  

  11.     int minutes = ts1.minutes(); // 45  

  12.     int totalMins = ts1.totalMinutes(); // 2145  

  13.     int seconds = ts1.seconds(); // 22  

  14.     int totalSecs = ts1.totalSeconds(); // 128722  

  15.     return 0;  

  16. }  

    下面来看一个DateTime,LocalDateTime和Timespan的混合例子:

[cpp] view plaincopy

  1. #include "Poco/DateTime.h"  

  2. #include "Poco/Timespan.h"  

  3. using Poco::DateTime;  

  4. using Poco::Timespan;  

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

  6. {  

  7.     // what is my age?  

  8.     DateTime birthdate(1973, 9, 12, 2, 30); // 1973-09-12 02:30:00  

  9.     DateTime now;  

  10.     Timespan age = now - birthdate;  

  11.     int days = age.days(); // in days  

  12.     int hours = age.totalHours(); // in hours  

  13.     int secs = age.totalSeconds(); // in seconds  

  14.     // when was I 10000 days old?  

  15.     Timespan span(10000*Timespan::DAYS);  

  16.     DateTime dt = birthdate + span;  

  17.     return 0;  

  18. }  

6. Timezone类

Poco::Timezone提供静态函数用于获取时区信息和夏令时信息。
1. 时区差
2. 是否采用夏时制(daylight saving time (DST))
3. 时区名

成员函数: 1. int utcOffset()
    返回本地时间相对于UTC时间的差值(精度秒)。不包括夏令时偏移:
    (localtime = UTC + utcOffset())
2. int dst()
    返回夏令时偏移。通常是固定值3600秒。0的话表示无夏令时。
3. bool isDst(const Timestamp& timestamp)
    对于给定的timestamp时间测试,是否使用夏令时
4. int tzd()
    返回本地时间相对于UTC时间的差值(精度秒)。包括夏令时偏移:
   (tzd = utcOffset() + dst())
5. std::string name()
   返回当前的时区名
6. std::string standardName()
    返回当前的标准时区名(如果不采用夏令时)
7. std::string dstName()
   返回当前的时区名(如果采用夏令时)
8. 时区名的返回依赖于操作系统,并且名称不具有移植性,仅作显示用。

        下面是一个使用的例子:

[cpp] view plaincopy

  1. #include "Poco/Timezone.h"  

  2. #include "Poco/Timestamp.h"  

  3. using Poco::Timezone;  

  4. using Poco::Timestamp;  

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

  6. {  

  7.     int utcOffset = Timezone::utcOffset();  

  8.     int dst = Timezone::dst();  

  9.     bool isDst = Timezone::isDst(Timestamp());  

  10.     int tzd = Timezone::tzd();  

  11.     std::string name = Timezone::name();  

  12.     std::string stdName = Timezone::standardName();  

  13.     std::string dstName = Timezone::dstName();  

  14.     return 0;  

  15. }  

6. Poco::DateTimeFormatter类

Poco::DateTimeFormatter用来定义当Timestamp,DateTime, LocalDateTime and Timespan转换为字符串时所需的日期和事件格式。Poco::DateTimeFormatter的作用和strftime()是类似的。Poco::DateTimeFormat内部定义了一些约定的格式。
1. ISO8601_FORMAT (2005-01-01T12:00:00+01:00)
2. RFC1123_FORMAT (Sat, 1 Jan 2005 12:00:00 +0100)
3. SORTABLE_FORMAT (2005-01-01 12:00:00)
4. For more information, please see the referencedocumentation.

成员函数: 所有的DateTimeFormatter函数都是静态的。

        下面是一个使用的例子:

[cpp] view plaincopy

  1. #include "Poco/Timestamp.h"  

  2. #include "Poco/Timespan.h"  

  3. #include "Poco/DateTimeFormatter.h"  

  4. #include "Poco/DateTimeFormat.h"  

  5. using Poco::DateTimeFormatter;  

  6. using Poco::DateTimeFormat;  

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

  8. {  

  9.     Poco::DateTime dt(2006, 10, 22, 15, 22, 34);  

  10.     std::string s(DateTimeFormatter::format(dt, "%e %b %Y %H:%M"));  

  11.     // "22 Oct 2006 15:22"  

  12.     Poco::Timestamp now;  

  13.     s = DateTimeFormatter::format(now, DateTimeFormat::SORTABLE_FORMAT);  

  14.     // "2006-10-30 09:27:44"  

  15.     Poco::Timespan span(5, 11, 33, 0, 0);  

  16.     s = DateTimeFormatter::format(span, "%d days, %H hours, %M minutes");  

  17.     // "5 days, 11 hours, 33 minutes"  

  18.     return 0;  

  19. }  

7. Poco::DateTimeParser类

        Poco::DateTimeParser用来从字符串中解析时间和日期。下面是其一个例子:

[cpp] view plaincopy

  1. #include "Poco/DateTimeParser.h"  

  2. #include "Poco/DateTime.h"  

  3. #include "Poco/DateTimeFormat.h"  

  4. #include "Poco/LocalDateTime.h"  

  5. #include "Poco/Timestamp.h"  

  6. using Poco::DateTimeParser;  

  7. using Poco::DateTimeFormat;  

  8. using Poco::DateTime;  

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

  10. {  

  11.     std::string s("Sat, 1 Jan 2005 12:00:00 GMT");  

  12.     int tzd;  

  13.     DateTime dt;  

  14.     DateTimeParser::parse(DateTimeFormat::RFC1123_FORMAT, s, dt, tzd);  

  15.     Poco::Timestamp ts = dt.timestamp();  

  16.     Poco::LocalDateTime ldt(tzd, dt);  

  17.     bool ok = DateTimeParser::tryParse("2006-10-22", dt, tzd);  

  18.     ok = DateTimeParser::tryParse("%e.%n.%Y", "22.10.2006", dt, tzd);  

  19.     return 0;  

  20. }  

8. Stopwatch类

        Stopwatch用来测量时间差值,精度为微秒.下面是其定义:

[cpp] view plaincopy

  1. class Foundation_API Stopwatch  

  2.     /// A simple facility to measure time intervals  

  3.     /// with microsecond resolution.  

  4.     ///  

  5.     /// Note that Stopwatch is based on the Timestamp  

  6.     /// class. Therefore, if during a Stopwatch run,  

  7.     /// the system time is changed, the measured time  

  8.     /// will not be correct.  

  9. {  

  10. public:  

  11.     Stopwatch();  

  12.     ~Stopwatch();  

  13.   

  14.   

  15.     void start();  

  16.         /// Starts (or restarts) the stopwatch.  

  17.           

  18.     void stop();  

  19.         /// Stops or pauses the stopwatch.  

  20.       

  21.     void reset();  

  22.         /// Resets the stopwatch.  

  23.           

  24.     void restart();  

  25.         /// Resets and starts the stopwatch.  

  26.           

  27.     Timestamp::TimeDiff elapsed() const;  

  28.         /// Returns the elapsed time in microseconds  

  29.         /// since the stopwatch started.  

  30.           

  31.     int elapsedSeconds() const;  

  32.         /// Returns the number of seconds elapsed  

  33.         /// since the stopwatch started.  

  34.   

  35.   

  36.     static Timestamp::TimeVal resolution();  

  37.         /// Returns the resolution of the stopwatch.  

  38.   

  39.   

  40. private:  

  41.     Stopwatch(const Stopwatch&);  

  42.     Stopwatch& operator = (const Stopwatch&);  

  43.   

  44.   

  45.     Timestamp           _start;  

  46.     Timestamp::TimeDiff _elapsed;  

  47.     bool                _running;  

  48. };  

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

1. 异常处理

       C++同C语言相比,提供了异常机制。通过使用try,catch关键字可以捕获异常,这种机制使得程序员在程序异常发生时,可以通过判断异常类型,来决定程序是否继续执行,并在程序结束之前优雅的释放各类资源。当然对于C++的异常机制也存在着很多的争议。在这里,并不对此展开讨论,只介绍一下Poco中的异常类。

        Poco中的异常类:
        1. 所有的异常类都是Poco::Exception的子类。
        2. Poco::Exception继承自std::exception类。
        3. Foundation库中涉及的异常类,包括了下面一些:
                  a) Poco::LogicException类负责处理程序错误,包括了:
                          AssertionViolationException
                         NullPointerException
                          NullValueException
                          BugcheckException
                          InvalidArgumentException
                          NotImplementedException
                          RangeException
                          IllegalStateException
                          InvalidAccessException
                          SignalException
                          UnhandledException
                  b) Poco::ApplicationException类负责处理应用程序相关的错误,即使用Poco库的用户自定义异常。
                  c) Poco::RuntimeException类负责处理程序运行时的错误,包括了:
                          RuntimeException
                          NotFoundException
                          ExistsException
                          TimeoutException
                          SystemException
                          RegularExpressionException
                          LibraryLoadException
                          LibraryAlreadyLoadedException
                          NoThreadAvailableException
                          PropertyNotSupportedException
                          PoolOverflowException
                          NoPermissionException
                          OutOfMemoryException
                          DataException
                          DataFormatException
                          SyntaxException
                          CircularReferenceException
                          PathSyntaxException
                          IOException
                          ProtocolException
                          FileException
                          FileExistsException
                          FileNotFoundException
                          PathNotFoundException
                          FileReadOnlyException
                          FileAccessDeniedException
                          CreateFileException
                          OpenFileException
                          WriteFileException
                          ReadFileException
                          UnknownURISchemeException

成员函数及数据定义:
        1. Poco::Exception包括了一个名字,这是一个静态的字符串,用来描述异常本身。比如说LogicException名字为"Logic exception",TimeoutException名字为"Timeout"。
        2. Poco::Exception还包含了一个字符串消息,这是用来进一步描述异常的。使用的的人可以在运行时定义它。比如都是LogicException异常,函数一处抛出异常时可定义为"Function1",函数二处抛出时异常时可定义为用"Function2",它可以用来说明异常发生的具体位置和原因。
        3. 一个可选的嵌套异常类
        4. 构造函数:
                  a) 可以使用0个,1个或2个字符串参数来构造异常。在Poco::Exception内部存储的时候,第二个字符串会使用字符":"和第一个字符串串联。
                  b) 构造时如果使用了字符串和嵌套异常的方式,嵌套异常会被复制一份。
        5. Poco::Exception支持拷贝和赋值运算符
        6. const char* name()
                   返回异常的名称
        7. const std::string& message()
                   返回在构造时传入的消息字符串
        8. std::string displayText() const
                   同时返回异常名字和消息字符串,中间使用": "分隔
        9. const Exception* nested() const
                   如果存在嵌套异常的话,返回之歌指向嵌套异常的指针,否则返回0
        10. Exception* clone() const
                   返回一个异常的拷贝
        11. void rethrow() const
                   重新抛出异常

定义自己的异常:
        因为从Poco::Exception继承,去定义自己的异常时,工作非常的枯燥且重复(用户需要重载大量的虚函数),在库中提供了两个宏来完成这个工作:
                  POCO_DECLARE_EXCEPTION:用来申明异常宏
                  POCO_IMPLEMENT_EXCEPTION:用来定义异常宏的执行体

        两个宏分别定义如下:

// MyException.h

#include "Poco/Exception.h"

POCO_DECLARE_EXCEPTION(MyLib_API, MyException, Poco::Exception)

// MyException.cpp

#include "MyException.h"POCO_IMPLEMENT_EXCEPTION(MyException, Poco::Exception,"Something really bad happened…")

       宏展开分别为:

// MyException.h

#include "Poco/Exception.h"

POCO_DECLARE_EXCEPTION(MyLib_API, MyException, Poco::Exception)

class MyLib_API MyException: public Poco::Exception

{

public:

            MyException();

            MyException(const std::string& msg);

            MyException(const std::string& msg, const std::string& arg);

            MyException(const std::string& msg, const Poco::Exception& nested);

            MyException(const MyException& exc);

            ~MyException();

            MyException& operator = (const MyException& exc);

            const char* name() const;

            …

};

// MyException.cpp

#include "MyException.h"

POCO_IMPLEMENT_EXCEPTION(MyException, Poco::Exception,

"Something really bad happened…")

const char* MyException::name() const throw()

{

            return "Something really bad happened…";

}

        下面是一个例子:

#include "Poco/Exception.h"

#include

int main(int argc, char** argv)

{

            Poco::Exception* pExc = 0;

            try

            {

                        throw Poco::ApplicationException("just testing");

            }

            catch (Poco::Exception& exc)

            {

                        pExc = exc.clone();

            }

            try

            {

                        pExc->rethrow();

            }

            catch (Poco::Exception& exc)

            {

                        std::cerr << exc.displayText() << std::endl;

            }

            delete pExc;

            return 0;

}

2. 断言

       POCO库中提供了一些断言的宏来进行运行时检查,这些断言能够提供出错代码的行号和文件信息。
        1. Debugger::_assert(cond)
         如果cond ≠ true时,抛出一个AssertionViolationException异常。
        2. poco_assert_dbg(cond)
           同poco_assert类似,但是只在debug模式下起作用
        3. poco_check_ptr(ptr)
           如果ptr为空,则抛出NullPointerException异常
        4. poco_bugcheck(), poco_bugcheck_msg(string)
           抛出BugcheckException异常

        POCO的断言类在debug调试模式下(比如在VisualC++)中时,会触发一个breakpoint。比如:

void foo(Bar* pBar)

{

            poco_check_ptr (pBar);

            …

}

void baz(int i)

{

            poco_assert (i >= 1 && i < 3);

            switch (i)

            {

            case 1:

                        …

                                     break;

            case 2:

                        …

                                     break;

            default:

                        poco_bugcheck_msg("i has invalid value");

            }

}

        这主要是因为Poco中的断言类是通过Poco::Debugger去实现的,在Poco::Debugger底层调用了不同操作系统的API,去判断程序是否处于调试状态。如VC下,调用了

BOOL WINAPI IsDebuggerPresent(VOID);

VOID WINAPI DebugBreak(VOID);

3. NDC(Nested Diagnostic Context)

3.1  概述

       NestedDiagnosticContext是为了多线程诊断而设计的。我们在写程序时,一般都需要同时处理多个线程。为了更加便捷的处理多线程情况,为每个线程产生各自的日志。Neil Harrison 在他的书中"Patterns for Logging Diagnostic Messages," in Pattern Languages of Program Design 3, edited by R.Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997) 中提出了一个方法。独特地标记每个日志请求,用户把上下文信息送入NDC,NDC是 Nested Diagnostic Context的缩写。在这本书里提到了3种日志方法,分别是:
        1. DiagnosticLogger
         分离日志和程序其他模块
        2. TransactionalBuckets
        事务桶,为事务单独建立日志
        3. TypedDiagnostics
        类型化诊断,为所有的诊断信息提供统一的展现

我们还是回到Poco中的NDC上。在Poco中和NDC相关的内容包括了,NestedDiagnosticContext类,NDCScope类,宏poco_ndc和poco_ndc_dbg。其中NestedDiagnosticContext类维护一个NDC对象,其中包括了上下文的栈信息,有函数方法名,源文件代码文件名,行号。宏poco_ndc(func) or poco_ndc_dbg(func)申明了一个NDCScope对象。而NDCScope对象则完成了上下文的入栈工作。下面是一个例子:

#include "Poco/NestedDiagnosticContext.h"

#include

void f1()

{

            poco_ndc(f1);

            Poco::NDC::current().dump(std::cout);

}

void f2()

{

            poco_ndc(f2);

            f1();

}

int main(int argc, char** argv)

{

            f2();

            return 0;

}

3.2 实现

3.2.1 线程本地存储

       在Poco中实现时,用了一些小技巧,即线程本地存储。我们来看Poco中TLS的类图:

        CurrentThreadHolder类是TLS实现的具体类,在每个Thread对象中包含了一个CurrentThreadHolder对象。Thread创建的时候,CurrentThreadHolder会调用不同操作系统的API函数,获取并保存一个固定槽位,用于保存Thread对象的指针。
        每个Thread对象中还包含了一个ThreadLocalStorage对象。ThreadLocalStorage类用于保存具体的线程信息数据,它是一个TLSSlot对象的集合。通过泛型实现TLSSlot后,ThreadLocalStorage可用于保存任何数据的。

        使用了TLS技术后,调用Thread的静态函数current可以获取到每个线程对象Thread的指针,然后再通过这个Thread对象的指针,可以获取到ThreadLocalStorage对象,并最终获取或保存数据于TLSSlot中。

        通过类的静态函数获取类实例的指针,在C++中是不存在的,这需要操作系统支持,只有Thread对象才能做到这一点。

3.2.2 NDC

       在来看一张Poco中NDC类的类图:

        使用者通过调用宏poco_ndc和poco_ndc_dbg,来构建一个NDCScope对象。宏定义如下:

#define poco_ndc(func) \

            Poco::NDCScope _theNdcScope(#func, __LINE__, __FILE__)

#if defined(_DEBUG)

            #define poco_ndc_dbg(func) \

                        Poco::NDCScope _theNdcScope(#func, __LINE__, __FILE__)

#else

            #define poco_ndc_dbg(func)

#endif

        NDCScope实现了诊断信息上下文的入栈出栈工作,它通过调用NestedDiagnosticContext类的静态函数current实现了此功能。其定义如下:

inline NDCScope::NDCScope(const std::string& info)

{

            NestedDiagnosticContext::current().push(info);

}

inline NDCScope::NDCScope(const std::string& info, int line, const char* filename)

{

            NestedDiagnosticContext::current().push(info, line, filename);

}

inline NDCScope::~NDCScope()

{

            NestedDiagnosticContext::current().pop();

}

        NestedDiagnosticContext类的current()是个静态函数,其定义如下:

namespace

{

            static ThreadLocal ndc;

}

NestedDiagnosticContext& NestedDiagnosticContext::current()

{

            return ndc.get();

}

        而ThreadLocal是一个辅助类,用于获取线程对象的本地存储信息或者是主线程的本地存储信息。

template

class ThreadLocal

            /// This template is used to declare type safe thread

            /// local variables. It can basically be used like

            /// a smart pointer class with the special feature

            /// that it references a different object

            /// in every thread. The underlying object will

            /// be created when it is referenced for the first

            /// time.

            /// See the NestedDiagnosticContext class for an

            /// example how to use this template.

            /// Every thread only has access to its own

            /// thread local data. There is no way for a thread

            /// to access another thread's local data.

{

            typedef TLSSlot Slot;

public:

            ThreadLocal()

            {

            }

            ~ThreadLocal()

            {

            }

            C* operator -> ()

            {

                        return &get();

            }

            C& operator * ()

                        /// "Dereferences" the smart pointer and returns a reference

                        /// to the underlying data object. The reference can be used

                        /// to modify the object.

            {

                        return get();

            }

            C& get()

                        /// Returns a reference to the underlying data object.

                        /// The reference can be used to modify the object.

            {

                        TLSAbstractSlot*& p = ThreadLocalStorage::current().get(this);

                        if (!p) p = new Slot;

                        return static_cast(p)->value();

            }

private:

            ThreadLocal(const ThreadLocal&);

            ThreadLocal& operator = (const ThreadLocal&);

};

        到这里Poco中所有的NDC流程都被打通了,用户终于可以实现按线程打印日志信息了。

附录:

1.  The Gregorian Calendar: http://en.wikipedia.org/wiki/Gregorian_calendar
2. The Julian Calendar:http://en.wikipedia.org/wiki/Julian_calendar
3. The Julian Day:http://en.wikipedia.org/wiki/Julian_day
4. Coordinated Universal Time (UTC):http://en.wikipedia.org/wiki/UTC
5.  ISO 8601:http://en.wikipedia.org/wiki/ISO_8601

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

           在程序设计时,有时候我们需要生成随机数和数字摘要。在Poco库中,也提供了上述功能,下面我们一一叙述:

1. 随机数生成

          Poco中生成随机数的类为Poco::Random类。它根据PRNG(pseudo random number generator )算法设计,采用了一个累加的非线性反馈算法。PRNG算法可以产生0 ~ 2^31之间的随机数整数。
           在接口上Poco::Random提供了一些函数,可以使使用者直接得到其他形式的随机数。如char, bool, float 和 double 类型。另外Poco库中还提供了RandomInputStream类,用于Poco::Random类的流操作。

成员函数:
           1. void seed(Poco::UInt32 seed)
           根据给定的种子值生成随机数。

           2. void seed()
           使用任意值(从RandomInputStream类中获取)生成随机数。

           3. 默认的构造时,Poco::Random类采用当前的时间和日期生成随机数。如果想要更好的随机效果,需要显式的调用seed()方法

           4. UInt32 next()
           返回0 ~ 2^31之间的随机整数

           5. UInt32 next(UInt32 n)
           返回0 ~ n之间的随机整数

           6. char nextChar()
           返回随机Char值

           7. bool nextBool()
           返回随机bool值

           8. float nextFloat()
           返回随机float值,范围0 ~ 1

           9. double nextDouble()
           返回随机double值,范围0 ~ 1

           下面是关于Random的一个例子:

#include "Poco/Random.h"

#include "Poco/RandomStream.h"

#include

using Poco::Random;

using Poco::RandomInputStream;

int main(int argc, char** argv)

{

            Random rnd;

            rnd.seed();

            std::cout << "Random integer: " << rnd.next() << std::endl;

            std::cout << "Random digit: " << rnd.next(10) << std::endl;

            std::cout << "Random char: " << rnd.nextChar() << std::endl;

            std::cout << "Random bool: " << rnd.nextBool() << std::endl;

            std::cout << "Random double: " << rnd.nextDouble() << std::endl;

            RandomInputStream ri;

            std::string rs;

            ri >> rs;

            return 0;

}

2. 密码散列

          下面这段是Wiki上关于密码散列的介绍:
           A cryptographic hash function is ahash function with certain additional security properties to make itsuitable for use as a primitive in various information securityapplications, such as authentication and message integrity. Ahash function takes a long string (or message) of any length as inputand produces a fixed length string as output, sometimes termed a messagedigest or a digital fingerprint. Wikipedia

2.1 概述

          密码散列(cryptographic hash)是将目标文本转换成具有相同长度的、不可逆的杂凑字符串(或叫做消息摘要)。它有两个特点:
           1、哈希算法往往被设计成生成具有相同长度的文本
           2、哈希算法是不可逆的。(因为如果可逆,那么哈希就是世界上最强悍的压缩方式——能将任意大小的文件压缩成固定大小)

           密码散列是一个多对一映射,好的哈希算法应该对于输入的改变极其敏感。Poco中实现了被广泛使用的密码散列函数(cryptographic hash functions), 包括了MD4, 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 hmac(passphrase); // we'll compute a HMAC-SHA1

            hmac.update(message1);

            hmac.update(message2);

            const DigestEngine::Digest& digest = hmac.digest();

            // finish HMAC computation and obtain digest

            std::string digestString(DigestEngine::digestToHex(digest));

            // convert to a string of hexadecimal numbers

            return 0;

}

2.3 与DigestEngine类相关的流(DigestInputStream/DigestOutputStream)

          可以通过Poco::DigestInputStream和Poco::DigestOutputStream类对DigestEngine类进行输入输出操作。过程很简单,只要在在构造Stream时,把相关的DigestEngine类传入即可。需要注意的是,在向DigestOutputStream类写入后,要及时调用flush函数,以确保Stream把所有数据都输入进DigestEngine类。

           下面是相关的一个例子:

#include "Poco/DigestStream.h"

#include "Poco/MD5Engine.h"

using Poco::DigestOutputStream;

using Poco::DigestEngine;

using Poco::MD5Engine;

int main(int argc, char** argv)

{

            MD5Engine md5;

            DigestOutputStream ostr(md5);

            ostr << "This is some text";

            ostr.flush(); // Ensure everything gets passed to the digest engine

            const DigestEngine::Digest& digest = md5.digest(); // obtain result

            std::string result = DigestEngine::digestToHex(digest);

            return 0;

}

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

               既然作为一个框架性的库,自然会提供对于文件系统的操作。在Poco库中,封装了一些类去完成上述操作。这些类包括了:
              1. Poco::Path

             2. Poco::File

             3. Poco::TemporaryFile

             4. Poco::DirectoryIterator

             5. Poco::Glob

              这些类在实现上并没有什么特殊的注意点,主要是不同操作系统API的调用。如果想学习API函数的话,确实是一个不错的例子。在这里将主要介绍这些类的接口和使用,主要以翻译Poco的使用文档为主。

1. Poco::Path

1.1 路径:

              1. 在不同操作系统中,指明文件和目录所在位置的标示符是不一样的。
               2. 标示符的不一致,会造成代码在不同平台之间移植的困难。
               3. Poco::Path类抽象了不同标识符之间的区别,使程序员可以把注意力集中在业务的开发上。
               4. Poco::Path类支持Windows、Unix、OpenVMS操作系统。

1.2 Poco路径简介:

              Poco中的路径包括了
               1. 一个可选的节点(node)名:
                             a) 在Windows上,这是计算机在UNC(UniversalNaming Convention)路径中的名字
                             b) 在OpenVMS中,这代表一个集群系统中的节点名
                             c) 在Unix中,此名字未被使用。
               2. 一个可选的设备(device)名:
                             a) 在Windows上,这是一个驱动器盘符
                             b) 在OpenVMS上,这是存储盘符的名字
                             c) 在Unix,此名字未被使用。
               3. 一个目录名的列表
               4. 一个文件名(包括扩展名)和版本号(OpenVMS特有)

               Poco支持两种路径:
               1. 绝对路径
               以根目录为起点的描述资源的目录
               2. 相对目录
               以某一个确定路径为起点的描述资源的目录(通常这是用户的当前目录)

               相对目录可以被转换为绝对目录(反之,并不成立)。
               在Poco中路径的指向可以是一个目录也可以是一个文件。当路径指向目录时,文件名为空。

               下面是Poco中关于路径的一些例子:

    Path: C:\Windows\system32\cmd.exe

        Style: Windows

        Kind: absolute, to file

        Node Name: –

        Device Name: C

        Directory List: Windows, system32

        File Name: cmd.exe

        File Version: –

    Path: Poco\Foundation\

        Style: Windows

        Kind: relative, to directory

        Node Name: –

        Device Name: –

        Directory List: Poco, Foundation

        File Name: –

        File Version: –

    Path: \\www\site\index.html

        Style: Windows

        Kind: absolute, to file

        Node Name: www

        Device Name: –

        Directory List: site

        File Name: index.html

        File Version: –

    Path: /usr/local/include/Poco/Foundation.h

        Style: Unix

        Kind: absolute, to file

        Node Name: –

        Device Name: –

        Directory List: usr, local, include, Poco

        File Name: index.html

        File Version: –

    Path: ../bin/

        Style: Unix

        Kind: relative, to directory

        Node Name: –

        Device Name: –

        Directory List: .., bin

        File Name: –

        File Version: –

    Path: VMS001::DSK001:[POCO.INCLUDE.POCO]POCO.H;2

        Style: OpenVMS

        Kind: absolute, to file

        Node Name: VMS001

        Device Name: DSK001

        Directory List: POCO, INCLUDE, POCO

        File Name: POCO.H

        File Version: 2

1.3 类说明

              1. Poco::Path类在Poco库中代表了路径。
               2. Poco::Path类并不关心路径所指向的目标在文件系统中是否存在。这个工作由Poco::File类负责。
               3. Poco::Path支持值语义(copy函数和赋值函数),但不支持关系操作符。

               构建一个路径
               构建一个路径存在着两种方式:
               1. 从0开始构建,分别构建node、device、directory、file
               2. 通过一个包含着路径的字符串去解析

               在构建时,可以指定路径的格式:
                  a)PATH_UNIX
                  b)PATH_WINDOWS
                   c)PATH_VMS
                  d)PATH_NATIVE (根据当前系统格式判断)
                  e)PATH_GUESS (让Poco库自行判断)

               从0构造路径
               1. 创建一个空路径,使用默认的构造函数(默认情况下路径格式为"相对目录")或者构造时使用一个bool参数去指定路径格式(true = absolute, false = relative)
               2. 如果需要的话,使用下列赋值函数去设置节点和设备名
                             void setNode(const std::string& node)
                             void setDevice(const std::string& device)
               3. 添加路径名
                             void pushDirectory(const std::string&name)
               4. 设置文件名
                             void setFileName(const std::string& name)

               下面是一个例子:

#include "Poco/Path.h"

int main(int argc, char** argv)

{

            Poco::Path p(true); // path will be absolute

            p.setNode("VMS001");

            p.setDevice("DSK001");

            p.pushDirectory("POCO");

            p.pushDirectory("INCLUDE");

            p.pushDirectory("POCO");

            p.setFileName("POCO.H");

            std::string s(p.toString(Poco::Path::PATH_VMS));

            // "VMS001::DSK001:[POCO.INCLUDE.POCO]POCO.H"

            p.clear(); // start over with a clean state

            p.pushDirectory("projects");

            p.pushDirectory("poco");

            s = p.toString(Poco::Path::PATH_WINDOWS); // "projects\poco\"

            s = p.toString(Poco::Path::PATH_UNIX); // "projects/poco/"

            s = p.toString(); // depends on your platform

            return 0;

}

               从一个字符串中解析路径名
               1. Poco支持从一个字符串中解析路径名
                             Path(const std::string& path)
                             Path(const std::string& path, Stylestyle)
               如果函数调用时,路径格式style不被指定,将使用当前系统路径格式。
               2. 可以从另一个路径(指向目录名)和文件名,或者两个路径(第一个为绝对路径,第二个为相对路径)构造
                             Path(const Path& parent, conststd::string& fileName)
                             Path(const Path& parent, const Path&relative)

               路径也可以通过下列函数去构建
                             Path& assign(const std::string& path)
                             Path& parse(const std::string& path)
                             Path& assign(const std::string& path,Style style)
                             Path& parse(const std::string& path,Style style)

               如果路径非法的话,会抛出Poco::PathSyntaxException异常。想要测试一个路径字符串是否合法,可以使用tryParse()函数:
                             bool tryParse(const std::string& path)
                             bool tryParse(const std::string& path,Style style)

               下面是一个例子:

#include "Poco/Path.h"

using Poco::Path;

int main(int argc, char** argv)

{

            //creating a path will work independent of the OS

            Path p("C:\\Windows\\system32\\cmd.exe");

            Path p("/bin/sh");

            p = "projects\\poco";

            p = "projects/poco";

            p.parse("/usr/include/stdio.h", Path::PATH_UNIX);

            bool ok = p.tryParse("/usr/*/stdio.h");

            ok = p.tryParse("/usr/include/stdio.h", Path::PATH_UNIX);

            ok = p.tryParse("/usr/include/stdio.h", Path::PATH_WINDOWS);

            ok = p.tryParse("DSK$PROJ:[POCO]BUILD.COM", Path::PATH_GUESS);

            return 0;

}

               Poco::Path类提供了函数用于转换成为字符串:
               std::string toString()
               std::stringtoString(Style style)
               当然也可以使用下列函数得到路径不同部分的字符串:
              const std::string&getNode()
              const std::string&getDevice()
              const std::string&directory(int n) (also operator [])
              const std::string&getFileName()
               可以调用下列函数获取目录的深度:
               int depth() const

               通过下面的函数可以得到和设置文件的基本名和扩展名:
                             std::string getBaseName() const
                             void setBaseName(const std::string&baseName)
                             std::string getExtension() const
                             void setExtension(const std::string&extension)

               下面是一个例子:

#include "Poco/Path.h"

using Poco::Path;

int main(int argc, char** argv)

{

            Path p("c:\\projects\\poco\\build_vs80.cmd", Path::PATH_WINDOWS);

            std::string device(p.getDevice()); // "c"

            int n = p.depth(); // 2

            std::string dir1(p.directory(0)); // "projects"

            std::string dir2(p[1]); // "poco"

            std::string fileName(p[2]); // "build_vs80.cmd"

            fileName = p.getFileName();

            std::string baseName(p.getBaseName()); // "build_vs80"

            std::string extension(p.getExtension()); // "cmd"

            p.setBaseName("build_vs71");

            fileName = p.getFileName(); // "build_vs71.cmd"

            return 0;

}

   路径操作:
               1. Path&makeDirectory()
               确保路径的结尾是一个目录名。如果原路径有文件名存在的话,添加一个与文件名同名的目录,并清除文件名。
               2. Path& makeFile()
               确保路径的结尾是一个文件名。如果原路径是一个目录名,则把最后一个目录名变成文件名,并去除最后一个目录名。
               3. Path&makeParent()
                   Pathparent() const
               使路径指向它的父目录(如果存在文件名的话,清除文件名;否则的话则移除最后一个目录名)
               4. Path&makeAbsolute()
                   Path&makeAbsolute(const Path& base)
                   Pathabsolute() const
                   Pathabsolute(const Path& base)
               转换相对路径为绝对路径
               5. Path&append(const Path& path)
               添加路径
               6. Path&resolve(const Path& path)
               如果新的路径为绝对路径,则代替现有的路径;否则则在原路径下追加

路径属性:
               1. bool isAbsolute()const
               如果路径为绝对路径,返回true;否则为false
               2. bool isRelative()const
               如果路径为相对路径,返回true;否则为false
               3. bool isDirectory()const
               如果路径为目录,返回true;否则为false
               4. bool isFile() const
               如果路径为文件,返回true;否则为false

               下面是一个例子:

#include "Poco/Path.h"

using Poco::Path;

int main(int argc, char** argv)

{

            Path p("/usr/include/stdio.h", Path::PATH_UNIX);

            Path parent(p.parent());

            std::string s(parent.toString(Path::PATH_UNIX)); // "/usr/include/"

            Path p1("stdlib.h");

            Path p2("/opt/Poco/include/Poco.h", Path::PATH_UNIX);

            p.resolve(p1);

            s = p.toString(Path::PATH_UNIX); // "/usr/include/stdlib.h"

            p.resolve(p2);

            s = p.toString(Path::PATH_UNIX); // "/opt/Poco/include/Poco.h"

            return 0;

}

特殊的目录和文件
               Poco::Path提供了静态函数,用于获取系统中的一些特殊目录和文件
               1. std::string current()
               返回当前的工作目录
               2. std::string home()
               返回用户的主目录
               3. std::string temp()
               返回操作系统的零时目录
               4. std::string null()
               返回系统的空目录(e.g.,"/dev/null" or "NUL:")

               下面是一个例子:

#include "Poco/Path.h"

#include

using Poco::Path;

int main(int argc, char** argv)

{

            std::cout

                        << "cwd: " << Path::current() << std::endl

                        << "home: " << Path::home() << std::endl

                        << "temp: " << Path::temp() << std::endl

                        << "null: " << Path::null() << std::endl;

            return 0;

}

路径和环境变量
               在配置文件中的路径经常包含了环境变量。在传递此类路径给Poco::Path之间必须对路径进行扩展。
               对包含环境变量的路径扩展可以使用如下函数
                              std::string expand(conststd::string& path)
               函数会返回一个对环境变量进行扩充后的路径名。环境变量的格式会根据操作系统有所不同。(e.g., $VAR on Unix, %VAR% on Windows).在Unix上,同样会扩展"~/"为当前用户的主目录。

               下面是一个例子:

#include "Poco/Path.h"

using Poco::Path;

int main(int argc, char** argv)

{

            std::string config("%HOMEDRIVE%%HOMEPATH%\\config.ini");

            // std::string config("$HOME/config.ini");

            std::string expConfig(Path::expand(config));

            return 0;

}

文件系统主目录:
               voidlistRoots(std::vector& roots)
               会用所有挂载到文件系统的根目录来填充字符串数组。在windows上,为所有的驱动盘符。在OpenVMS上,是所有挂载的磁盘。在Unix上,为"/"。

查询文件:
               bool find(conststd::string& pathList, const std::string& name, Path& path)
               在指定的目录集(pathList)中搜索指定名称的文件(name)。pathList参数中如果指定了多个查找目录,目录之间必须使用分割符 (Windows平台上为";" , Unix平台上为":")。参数name中可以包含相对路径。如果文件在pathList指定的目录集中存在,找到文件的绝对路径会被放入path参数中,并且函数返回true,否则函数返回false,并且path不改变。
               这个函数也存在一个使用字符串向量,而不是路径列表的迭代器的变体。定义如下:
               boolfind(StringVec::const_iterator it, StringVec::const_iterator end,const std::string& name, Path& path)

               下面是一个例子:

#include "Poco/Path.h"

#include "Poco/Environment.h"

using Poco::Path;

using Poco::Environment;

int main(int argc, char** argv)

{

            std::string shellName("cmd.exe"); // Windows

            // std::string shellName("sh"); // Unix

            std::string path(Environment::get("PATH"));

            Path shellPath;

            bool found = Path::find(path, shellName, shellPath);

            std::string s(shellPath.toString());

            return 0;

}

2. Poco::File

同文件协同工作
               1. Poco仅支持与文件元数据协同工作。想要操作文件中的真实数据,需要使用标准库的文件流。
               2. 使用Poco库,你可以查出一个文件或者目录是否存在,是否可读或可写,文件何时创建和被修改,文件大小等信息。
               3. 通过Poco库,也可以修改文件属性,重命名文件,拷贝文件和删除文件。
               4. 通过Poco库,可以创建空文件(原子操作)和目录。

               所有同文件操作相关操作被定义于Poco::File中。为了创建一个Poco::File对象,需要提供一个路径作为参数。这个路径可以使用Poco::Path类,也可以是一个字符串。同时Poco::File也支持创建一个空对象,在稍后的操作中再设置路径。
               Poco::File支持所有的值语义,也支持所有的关系操作符 (==, !=, <, <=, >, >=)。关系操作符的结果是通过进行简单的文件路径比较而得到的。

               查询文件属性:
               1. bool exists() const
               如果文件存在返回true,否则为false
               2. bool canRead() const
               如果文件可读(用户对文件拥有足够权限)返回true,否则为false
               3. bool canWrite() const
               如果文件可写(用户对文件拥有足够权限)返回true,否则为false
               4. bool canExecute()const
               如果文件是可执行文件,返回true,否则为false
               5. bool isFile() const
               如果文件是常规文件(即不是目录或符号链接)返回为真,否则为false
               6. bool isLink() const
               如果文件是符号链接,返回为真,否则为false
               7. bool isDirectory()const
               如果文件是目录,返回为真,否则为false
               8. bool isDevice() const
               如果文件是设备,返回为真,否则为false
               9. bool isHidden() const
               如果文件属性为隐藏,返回为真,否则为false。(在Windows上文件属性为隐藏;Unit上使用"."开头的文件)
               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& files) const
                   voidlist(std::vector& files) const
               会把目录中所有的文件名填入给定的files数组。在其内部,会使用Poco::DirectoryIterator遍历目录

               下面是一个例子:

#include "Poco/File.h"

#include "Poco/Path.h"

#include

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 files;

            tmpDir.list(files);

            std::vector::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

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& files, int options =0)
               void glob(constPath& pattern, std::set& files, int options = 0)
               用符合给定pattern的所有文件填充给定的set集

               下面是一个例子:

#include "Poco/Glob.h"

#include

using Poco::Glob;

int main(int argc, char** argv)

{

            std::set files;

            Glob::glob("%WINDIR%\\system32\\*.exe", files);

            // Glob::glob("/usr/include/*/*.h", files);

            std::set::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

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: 不需要循环覆盖
              : 如果超过 字节的话,循环覆盖
               K: 如果超过 K字节的话,循环覆盖
               M: 如果超过 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 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: 不需要循环覆盖
              : 如果超过 字节的话,循环覆盖
               K: 如果超过 K字节的话,循环覆盖
               M: 如果超过 M字节的话,循环覆盖
              [day][hh:][mm]: 按照指定的日期和时间进行日志的循环覆盖
              daily/weekly/monthly: 按照日/周/月循环覆盖
               hours/weeks/months: 按照小时/周/月进行循环覆盖
          *archive:*归档日志的目录名
              number:从0开始自动增加的数字,被添加到日志文件名后。最新的日志文件数字总是0。
              timestamp: 时间戳以YYYYMMDDHHMMSS格式被添加到日志文件名后
              times:指定循环的时间是按照本地时间还是按照UTC时间。本地时间和utc时间都是可以接受的合法时间。
              compress:自动压缩存档文件。指定true或者false。
             purgeAge:指定归档日志的最大期限。当日志的生成时间超过此期限,将被删除。格式为 [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 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 pCons(new ConsoleChannel);

          AutoPtr 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 pCons(new ConsoleChannel);

          AutoPtr pFile(new SimpleFileChannel("test.log"));

          AutoPtr 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 pCons(new ConsoleChannel);

          AutoPtr pPF(new PatternFormatter);

          pPF->setProperty("pattern", "%Y-%m-%d %H:%M:%S %s: %t");

          AutoPtr 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 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、log1、…logn命名。ArchiveByTimestampStrategy完成了日志文件的时间戳命名,即程序产生的日志会以时间戳方式命名。
         在ArchiveStrategy类上还留有一个压缩接口,用来设置存档文件是否需要被压缩。在Poco中,内置了gzip压缩方式,这个具体由类ArchiveCompressor实现。关于这一点,我们会在以后介绍。

         对于文件删除的策略由PurgeStrategy类和其子类完成。PurgeByCountStrategy类,实现了按文件大小删除的策略。而PurgeByAgeStrategy实现了按文件存储时间删除的
策略。来看一段PurgeByAgeStrategy::purge动作的代码:

void PurgeByAgeStrategy::purge(const std::string& path)

{

            std::vector files;

            list(path, files);

            for (std::vector::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& 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.

            void removeChannel(Channel* pChannel);

                        /// Removes a channel.

            void log(const Message& msg);

                        /// Sends the given Message to all

                        /// attaches channels.

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

                        /// Sets or changes a configuration property.

                        ///

                        /// Only the "channel" property is supported, which allows

                        /// adding a comma-separated list of channels via the LoggingRegistry.

                        /// The "channel" property is set-only.

                        /// To simplify file-based configuration, all property

                        /// names starting with "channel" are treated as "channel".

            void close();

                        /// Removes all channels.

            int count() const;

                        /// Returns the number of channels in the SplitterChannel.

protected:

            ~SplitterChannel();

private:

            typedef std::vector ChannelVec;

            ChannelVec        _channels;

            mutable FastMutex _mutex;

};

         它的日志输出就是遍历所有的Channel对象,调用其输出。

void SplitterChannel::log(const Message& msg)

{

            FastMutex::ScopedLock lock(_mutex);

            for (ChannelVec::iterator it = _channels.begin(); it != _channels.end(); ++it)

            {

                        (*it)->log(msg);

            }

}

Logger:
         Logger是个接口类,它主要有3个功能:
         1. 它是一个Logger对象的工厂。调用静态函数get(conststd::string& name)可以获得对应的Logger对象。
         2. 它实现了日志逻辑上的继承体系。在其内部定义了一个静态变量_pLoggerMap。

   static std::map* _pLoggerMap;

        这个静态变量管理了所有的日志对象。
         3. 用户接口
         调用Logger对象的接口函数会触发其内部Channel对象的对应接口函数。比如说日志的记录动作:

void Logger::log(const Message& msg)

{

            if (_level >= msg.getPriority() && _pChannel)

            {

                        _pChannel->log(msg);

            }

}

2.8 概述

        应该说Poco库的日志功能实现的非常强大,同专门的日志库Logcpp相比也并不逊色。大家都知道,在Logcpp库中,category 、appender 和layout具有重要地位。做个对应比较的话:

        Logcpp中layout类控制输出信息的格式和样式,相当于Poco中的Formater。

        Logcpp中appender类用来输出信息到设备上,相当于Poco中的Channel。

        Logcpp中category类为用户接口,可以附加任意appender,这相当于Poco中的Logger类。

        在Poco库中,Logger和Channel的关系为包含关系,在Logcpp库中,category与appender同样也是,并且在两个库的实现上,其内部都使用了引用计数技术。对于这一点,大家想一想就明白,引用计数的开销最小。 

        如果说不同,同Logcpp相比,Poco库把消息单独抽象成Message类,在增加消息内容和扩展性的同时,也使Logger的输出接口变得稍复杂。

POCOC++库学习和分析-- 流 (一)

          流(Stream)是C++和C之间的一大区别。写C++的程序员都知道流的用法。在Poco库中,在标准流的基础上又扩充了一些流,分别是基于Base64和HexBinary的编解码流,使用zlib的数据压缩流,二进制的I/O流,文件流,以及一些其他的辅助流;另外Poco库还提供了一个扩展的结构,用于创建用户自定义流。
          Poco库中所有的流类都与标准c++库中的流兼容。并且在Poco库中,大多数流都仅仅是个过滤器,这意味着它们不会直接从设备中读取或者写入数据,通常情况下它们会链接到另一个流上。下面我们分别对它们进行介绍。

1. 标准c++流介绍

    在介绍Poco的流之前,我觉得有必要了解C++中的输入输出流,不然就会觉得Poco中的流很难理解。在看完C++的流结构后,自然会对Poco库中的流内容豁然开朗。我也一样。
          为了保证语言和平台无关,C++和C一样,不具备内部输入输出能力。语言的输入输出能力是和操作系统相关的,在最底层都是通过调用操作系统的I/O库实现。
          在C++的iostream流库中,存在着两个基本部分。分别是:
          1. 流:C++把输入和输出看作字节流。输入时,程序从输出流中抽取字节;输出时,程序将字节插入到输出流中。流充当了程序和流源或者流目标之间的桥梁。
          2. 缓冲区:缓冲区是用作中介的内存块,它是将信息从设备传输到程序或者从程序传输到设备的临时存储工具,用以匹配程序和设备之间速度的差距。从设计上说,增加了缓冲区,使的C++的iostream结构更具有扩展性。
          C++的输入输出类图

         下面对C++中各个流类的介绍主要来自于wiki以及网站cplusplus
 

1.1 ios_base

         ios_base类封装了C++标准中的流,并定义了在输入输出中不依赖于读写的数据类型的基本信息和行为,如格式化信息、异常状态、事件回调等。

          在类std::ios_base中,保存了下述关于流的信息:
          格式控制信息的枚举类型fmtflags ,影响到如何解释输入串行的格式、如何生成输出串行的格式,例如整数是用16进制还是10进制表示,浮点数是科学计数法还是定点形式;
          流的状态枚举类型iostate,如数据是否完整、是否到达流的末尾、是否读写失败等;
          流的打开方式枚举类型openmode,如读取、写入、追加、创建时删除原内容、二进制打开、
          流的定位位置枚举类型seekdir,如开始位置、当前位置、结尾位置等。
          流的事件枚举类型event,如“擦除”事件erase_event,改变locale设置事件imbue_event,复制格式事件copyfmt_event。
          流的私有的其它额外保存的数据,为一个long型数组与一个指针数组。
          一个成员类failure,用于作为C++标准中,流输入输出类库抛出的各种异常的基类。
          一个成员类Init,用于封装cout、cin、wcout等8个静态对象的初始化函数。

成员函数包括: 格式化:

         1. 读取/设置流的格式

fmtflags flags() const;

fmtflags flags (fmtflags fmtfl);

   例子:

// modify flags

#include      // std::cout, std::ios

int main () {

  std::cout.flags ( std::ios::right | std::ios::hex | std::ios::showbase );

  std::cout.width (10);

  std::cout << 100 << '\n';

  return 0;

}

          2. 设置流的格式,与原有格式合并

fmtflags setf (fmtflags fmtfl);

fmtflags setf (fmtflags fmtfl, fmtflags mask);

   例子:

// modifying flags with setf/unsetf

#include      // std::cout, std::ios

int main () {

  std::cout.setf ( std::ios::hex, std::ios::basefield );  // set hex as the basefield

  std::cout.setf ( std::ios::showbase );                  // activate showbase

  std::cout << 100 << '\n';

  std::cout.unsetf ( std::ios::showbase );                // deactivate showbase

  std::cout << 100 << '\n';

  return 0;

}

输出:
         Output:
         0x64
         64

          3. 根据参数mask,清除流的格式的某些位(bit)

void unsetf (fmtflags mask);

   例子:

// modifying flags with setf/unsetf

#include      // std::cout, std::ios

int main () {

  std::cout.setf ( std::ios::hex, std::ios::basefield );  // set hex as the basefield

  std::cout.setf ( std::ios::showbase );                  // activate showbase

  std::cout << 100 << '\n';

  std::cout.unsetf ( std::ios::showbase );                // deactivate showbase

  std::cout << 100 << '\n';

  return 0;

}

输出:
         0x64
         64

          4. 读取/设置显示浮点数时的精度

streamsize precision() const;

streamsize precision (streamsize prec);

   例子:

// modify precision

#include      // std::cout, std::ios

int main () {

  double f = 3.14159;

  std::cout.unsetf ( std::ios::floatfield );                // floatfield not set

  std::cout.precision(5);

  std::cout << f << '\n';

  std::cout.precision(10);

  std::cout << f << '\n';

  std::cout.setf( std::ios::fixed, std:: ios::floatfield ); // floatfield set to fixed

  std::cout << f << '\n';

  return 0;

}

输出:
         3.1416
         3.14159
         3.1415900000

          5. 读取/设定流的输出数据的显示宽度

streamsize width() const;

streamsize width (streamsize wide);

   例子:

// field width

#include      // std::cout, std::left

int main () {

  std::cout << 100 << '\n';

  std::cout.width(10);

  std::cout << 100 << '\n';

  std::cout.fill('x');

  std::cout.width(15);

  std::cout << std::left << 100 << '\n';

  return 0;

}

输出:
         100
                100
         100xxxxxxxxxxxx

          语言环境:
          1. 给流设置本地语言环境

locale imbue (const locale& loc);

   例子:

// imbue example

#include      // std::cout

#include        // std::locale

int main()

{

  std::locale mylocale("");   // get global locale

  std::cout.imbue(mylocale);  // imbue global locale

  std::cout << 3.14159 << '\n';

  return 0;

}

输出:

        3,14159

         2. 获取当前使用语言环境

locale getloc() const;

1.2 basic_ios

         basic_ios定义出“与字符类型及其相应字符特性相关”的streamclass的共同属性,其中包括清除流状态、设置流状态、拷贝流标志、返回或设置流缓冲区指针、设置本地化相关信息、返回或设置填充字符、字符转换,还包括了stream所用的缓冲器.
          basic_ios在其内部定义了一个指向streambuf的指针。

template

class basic_ios

     : public ios_base

{

     //C++标准库封装了一个缓冲区类streambuf,以供输入输出流对象使用。

     //每个标准C++输出输出流对象都包含一个指向streambuf的指针,

     basic_streambuf<_Elem, _Traits>*_Mystrbuf;

     // ….

}

          成员函数包括:

         1. 状态标记函数:

bool good() const; //检查流状态位是否为good

bool eof() const; //检查流状态位是否为eof,eofbit位被标志

bool fail() const; //检查流状态位是否为fail,failbit或者badbit被标志

bool bad() const; //检查流状态位是否为bad,badbit位被标志

iostate rdstate() const; //返回流状态位

          有两种方法可以获得输入/输出的状态信息。一种方法是通过调用rdstate()函数,它返回当前状态的错误标记。另一种方法则是使用good(), eof(),fail(), bad()函数来检测相应的输入/输出状态。
          状态位和函数返回值关系如下表:

iostate value (member constant)

indicates

functions to check state flags

good()

eof()

fail()

bad()

rdstate()

goodbit

No errors (zero value iostate)

true

false

false

false

goodbit

eofbit

End-of-File reached on input operation

false

true

false

false

eofbit

failbit

Logical error on i/o operation

false

false

true

false

failbit

badbit

Read/writing error on i/o operation

false

false

true

true

badbit

         例子:

// error state flags

#include      // std::cout, std::ios

#include       // std::stringstream

void print_state (const std::ios& stream) {

  std::cout << " good()=" << stream.good();

  std::cout << " eof()=" << stream.eof();

  std::cout << " fail()=" << stream.fail();

  std::cout << " bad()=" << stream.bad();

}

int main () {

  std::stringstream stream;

  stream.clear (stream.goodbit);

  std::cout << "goodbit:"; print_state(stream); std::cout << '\n';

  stream.clear (stream.eofbit);

  std::cout << " eofbit:"; print_state(stream); std::cout << '\n';

  stream.clear (stream.failbit);

  std::cout << "failbit:"; print_state(stream); std::cout << '\n';

  stream.clear (stream.badbit);

  std::cout << " badbit:"; print_state(stream); std::cout << '\n';

  return 0;

}

输出:
         goodbit: good()=1 eof()=0 fail()=0 bad()=0
         eofbit: good()=0 eof()=1 fail()=0 bad()=0
         failbit: good()=0 eof()=0 fail()=1 bad()=0
         badbit: good()=1 eof()=0 fail()=1 bad()=1

        2. oprator!()

bool operator!() const;

        如果没有错误标记被设置(failbit或badbit),返回true,否则返回false

        3. 设置/清除状态

void setstate (iostate state);

void clear (iostate state = goodbit);

        有两种方法可以设置输入/输出的状态信息。clear()函数可以使流状态将按照ios_base::iostate所描述的样子进行设置。ios::failbit、ios::badbit、ios::eofbit、ios::goodbit均为常量,它们中的任何一个都代表了一种流状态,或可被称为“输入状态标记位常量”。它们不是failbit、badbit、eofbit、goodbit这四个标记位的存贮变量。标记为常量的状态如上表所述。

         clear() 函数作用是:将流状态设置成括号内参数所代表的状态,强制覆盖掉流的原状态。

        例子:

// clearing errors

#include      // std::cout

#include       // std::fstream

int main () {

  char buffer [80];

  std::fstream myfile;

  myfile.open ("test.txt",std::fstream::in);

  myfile << "test";

  if (myfile.fail())

  {

    std::cout << "Error writing to test.txt\n";

    myfile.clear();

  }

  myfile.getline (buffer,80);

  std::cout << buffer << " successfully read from file.\n";

  return 0;

}

         setstate()函数的作用是:它并不强制覆盖流的原状态,而是将括号内参数所代表的状态叠加到原始状态上。它相当于:

void basic_ios::setstate (iostate state) {

  clear(rdstate()|state);

}

        4. 拷贝格式:

basic_ios& copyfmt (const basic_ios& rhs);

         例子:

// copying formatting information

#include      // std::cout

#include       // std::ofstream

int main () {

  std::ofstream filestr;

  filestr.open ("test.txt");

  std::cout.fill ('*');

  std::cout.width (10);

  filestr.copyfmt (std::cout);

  std::cout << 40;

  filestr << 40;

  return 0;

}

        5. 设置或获取填充字符

char_type fill() const;

char_type fill (char_type fillch);

         例子:

// using the fill character

#include      // std::cout

int main () {

  char prev;

  std::cout.width (10);

  std::cout << 40 << '\n';

  prev = std::cout.fill ('x');

  std::cout.width (10);

  std::cout << 40 << '\n';

  std::cout.fill(prev);

  return 0;

}

输出:
                 40
         xxxxxxxx40

        6. 返回和设置当前流的异常标志

iostate exceptions() const;

void exceptions (iostate except);

         例子:

// basic_ios::exceptions

#include      // std::cerr

#include       // std::ifstream

int main () {

  std::ifstream file;

  file.exceptions ( std::ifstream::failbit | std::ifstream::badbit );

  try {

    file.open ("test.txt");

    while (!file.eof()) file.get();

  }

  catch (std::ifstream::failure e) {

    std::cerr << "Exception opening/reading file";

  }

  file.close();

  return 0;

}

         7. 返回和设置绑定的输出流(绑定流是一个输出流,输出流将在流的每一次I/O操作前先被输出)。“绑定”的效果也就相当于,每当被“绑定”的对象有输入或输出操作时,会先自动刷新“绑定”对象的缓冲区,以达到实时的效果。

basic_ostream* tie() const;

basic_ostream* tie (basic_ostream* tiestr);

         例子:

// redefine tied object

#include      // std::ostream, std::cout, std::cin

#include       // std::ofstream

int main () {

  std::ostream *prevstr;

  std::ofstream ofs;

  ofs.open ("test.txt");

  std::cout << "tie example:\n";

  *std::cin.tie() << "This is inserted into cout";

  prevstr = std::cin.tie (&ofs);

  *std::cin.tie() << "This is inserted into the file";

  std::cin.tie (prevstr);

  ofs.close();

  return 0;

}

输出:
         tie example:
         This is inserted into cout

         8. 返回或设置流缓冲区指针。用户可以通过调用rdbuf()成员函数获得该指针,从而直接访问底层streambuf对象。因此,可以直接对底层缓冲区进行数据读写,从而跳过上层的格式化输入输出操作。

basic_streambuf* rdbuf() const;

basic_streambuf* rdbuf (basic_streambuf* sb);

         例子:

// redirecting cout's output thrrough its stream buffer

#include      // std::streambuf, std::cout

#include       // std::ofstream

int main () {

  std::streambuf *psbuf, *backup;

  std::ofstream filestr;

  filestr.open ("test.txt");

  backup = std::cout.rdbuf();     // back up cout's streambuf

  psbuf = filestr.rdbuf();        // get file's streambuf

  std::cout.rdbuf(psbuf);         // assign streambuf to cout

  std::cout << "This is written to the file";

  std::cout.rdbuf(backup);        // restore cout's original streambuf

  filestr.close();

  return 0;

}

         9. 字符转换
         返回宽字符wc对应的字符dfault,在其内部使用了流对应的locale对象做转换

char narrow (char_type wc, char dfault) const;

        返回c对应的宽字符

char_type widen (char c) const;

1.3  输入流( istream)

        istream是普通输入流类和用于其它输入流的基类。

         输入流的成员函数包括:

         1. 构造函数

explicit istream (streambuf* sb);

         例子:

// istream constructor

#include      // std::ios, std::istream, std::cout

#include       // std::filebuf

int main () {

  std::filebuf fb;

  if (fb.open ("test.txt",std::ios::in))

  {

    std::istream is(&fb);

    while (is)

      std::cout << char(is.get());

    fb.close();

  }

  return 0;

}

         2. 重载析取运算符

istream& operator>> (bool& val);

istream& operator>> (short& val);

istream& operator>> (unsigned short& val);

istream& operator>> (int& val);

istream& operator>> (unsigned int& val);

istream& operator>> (long& val);

istream& operator>> (unsigned long& val);

istream& operator>> (float& val);

istream& operator>> (double& val);

istream& operator>> (long double& val);

istream& operator>> (void*& val);

stream buffers (2)     

istream& operator>> (streambuf* sb );

manipulators (3)       

istream& operator>> (istream& (*pf)(istream&));

istream& operator>> (ios& (*pf)(ios&));

istream& operator>> (ios_base& (*pf)(ios_base&));

         例子:

// example on extraction

#include      // std::cin, std::cout, std::hex

int main () {

  int n;

  std::cout << "Enter a number: ";

  std::cin >> n;

  std::cout << "You have entered: " << n << '\n';

  std::cout << "Enter a hexadecimal number: ";

  std::cin >> std::hex >> n;         // manipulator

  std::cout << "Its decimal equivalent is: " << n << '\n';

  return 0;

}

         3. 无格式输入:

streamsize gcount() const;

        获取最后一次执行析取操作所读取的字符串数目

        例子:

// cin.gcount example

#include      // std::cin, std::cout

int main () {

  char str[20];

  std::cout << "Please, enter a word: ";

  std::cin.getline(str,20);

  std::cout << std::cin.gcount() << " characters read: " << str << '\n';

  return 0;

}

输出:
         Please, enter a word: simplify
         9 characteres read: simplify

         4. 从流中以无格式输入方式析取字符串

int get()

istream& get (char& c)

istream& get (char* s, streamsize n)

istream& get (char* s, streamsize n, char delim)

istream& get (streambuf& sb)

istream& get (streambuf& sb, char delim)

         例子:

// istream::get example

#include      // std::cin, std::cout

#include       // std::ifstream

int main () {

  char str[256];

  std::cout << "Enter the name of an existing text file: ";

  std::cin.get (str,256);    // get c-string

  std::ifstream is(str);     // open file

  while (is.good())          // loop while extraction from file is possible

  {

    char c = is.get();       // get character from file

    if (is.good())

      std::cout << c;

  }

  is.close();                // close file

  return 0;

}

         5. 从流中以无格式输入方式析取字符串(直至提取到指定的分隔字符,或已向流中写入最大长度的n个字符(包括终止空字符)),并存储内容进入c风格的字符串中。

istream& getline (char* s, streamsize n );

istream& getline (char* s, streamsize n, char delim );

         例子:

// istream::getline example

#include      // std::cin, std::cout

int main () {

  char name[256], title[256];

  std::cout << "Please, enter your name: ";

  std::cin.getline (name,256);

  std::cout << "Please, enter your favourite movie: ";

  std::cin.getline (title,256);

  std::cout << name << "'s favourite movie is " << title;

  return 0;

}

         6. 从流中提取字符输入序列(直到n个字符被提取,或遇见一个指定的字符分隔符),并丢弃。

istream& ignore (streamsize n = 1, int delim = EOF);

         例子:

// istream::ignore example

#include      // std::cin, std::cout

int main () {

  char first, last;

  std::cout << "Please, enter your first name followed by your surname: ";

  first = std::cin.get();     // get one character

  std::cin.ignore(256,' ');   // ignore until space

  last = std::cin.get();      // get one character

  std::cout << "Your initials are " << first << last << '\n';

  return 0;

}

         7. 返回输入序列中的下一个字符,但不析取:字符将在下一次析取操作中从流中提取。

int peek();

         例子:

// istream::peek example

#include      // std::cin, std::cout

#include        // std::string

int main () {

  std::cout << "Please, enter a number or a word: ";

  char c = std::cin.peek();

  if ( (c >= '0') && (c <= '9') )

  {

    int n;

    std::cin >> n;

    std::cout << "You entered the number: " << n << '\n';

  }

  else

  {

    std::string str;

    std::getline (std::cin, str);

    std::cout << "You entered the word: " << str << '\n';

  }

  return 0;

}

输出:
         Please, enter a number or a word: foobar
         You entered the word: foobar

         8. 从流中析取n个字符,并把它们存储到s指向的字符数组

istream& read (char* s, streamsize n);

         例子:

// read a file into memory

#include      // std::cout

#include       // std::ifstream

int main () {

  std::ifstream is ("test.txt", std::ifstream::binary);

  if (is) {

    // get length of file:

    is.seekg (0, is.end);

    int length = is.tellg();

    is.seekg (0, is.beg);

    char * buffer = new char [length];

    std::cout << "Reading " << length << " characters… ";

    // read data as a block:

    is.read (buffer,length);

    if (is)

      std::cout << "all characters read successfully.";

    else

      std::cout << "error: only " << is.gcount() << " could be read";

    is.close();

    // …buffer contains the entire file…

    delete[] buffer;

  }

  return 0;

}

输出:
         Reading 640 characters… all characters readsuccessfully.

         9. 从流中析取n个字符,并把它们存储到s指向的字符数组。在内部关联的缓冲区(如果存在的话)用完时,操作返回,即使文件结束字符尚未达到。这个函数主要用于从某些类型的异步源中读取数据。由于析取操作会在内部缓冲区耗尽时终止,从而避免了潜在的延时。

streamsize readsome (char* s, streamsize n);

         10. 把指定字符还给输入流,使得字符能够被重新析取

istream& putback (char c);

         例子:

// istream::putback example

#include      // std::cin, std::cout

#include        // std::string

int main () {

  std::cout << "Please, enter a number or a word: ";

  char c = std::cin.get();

  if ( (c >= '0') && (c <= '9') )

  {

    int n;

    std::cin.putback (c);

    std::cin >> n;

    std::cout << "You entered a number: " << n << '\n';

  }

  else

  {

    std::string str;

    std::cin.putback (c);

    getline (std::cin,str);

    std::cout << "You entered a word: " << str << '\n';

  }

  return 0;

}

输出:
         Please, enter a number or a word: pocket
         You entered a word: pocket

         11. 把上一个析取的字符还给输入流,使得字符能够被重新析取

istream& unget();

         例子:

// istream::unget example

#include      // std::cin, std::cout

#include        // std::string

int main () {

  std::cout << "Please, enter a number or a word: ";

  char c = std::cin.get();

  if ( (c >= '0') && (c <= '9') )

  {

    int n;

    std::cin.unget();

    std::cin >> n;

    std::cout << "You entered a number: " << n << '\n';

  }

  else

  {

    std::string str;

    std::cin.unget();

    getline (std::cin,str);

    std::cout << "You entered a word: " << str << '\n';

  }

  return 0;

}

输出:
         Please, enter a number or a word: 7791
         You entered a number: 7791

         12. 返回当前输入流字符的位置:

streampos tellg();

         例子:

// read a file into memory

#include      // std::cout

#include       // std::ifstream

int main () {

  std::ifstream is ("test.txt", std::ifstream::binary);

  if (is) {

    // get length of file:

    is.seekg (0, is.end);

    int length = is.tellg();

    is.seekg (0, is.beg);

    // allocate memory:

    char * buffer = new char [length];

    // read data as a block:

    is.read (buffer,length);

    is.close();

    // print content:

    std::cout.write (buffer,length);

    delete[] buffer;

  }

  return 0;

}

         13. 设置下一个析取字符串在输入流中的位置

istream& seekg (streampos pos);

istream& seekg (streamoff off, ios_base::seekdir way);

同步
         1. 同步流相关的缓冲区buf

int sync();

  例子:

// syncing input stream

#include      // std::cin, std::cout

int main () {

  char first, second;

  std::cout << "Please, enter a word: ";

  first = std::cin.get();

  std::cin.sync();

  std::cout << "Please, enter another word: ";

  second = std::cin.get();

  std::cout << "The first word began by " << first << '\n';

  std::cout << "The second word began by " << second << '\n';

  return 0;

}

输出:
         Please, enter a word: test
         Please enter another word: text
         The first word began by t
         The second word began by t

1.4. 输出流(ostream)

        ostream是普通输出流类和用于其它输出流类的基类。

         1. 构造函数

explicit ostream (streambuf* sb);

         例子:

// ostream constructor

#include      // std::cout, std::ostream, std::ios

#include       // std::filebuf

int main () {

  std::filebuf fb;

  fb.open ("test.txt",std::ios::out);

  std::ostream os(&fb);

  os << "Test sentence\n";

  fb.close();

  return 0;

}

         2. 重载插入运算符

ostream& operator<< (bool val);

ostream& operator<< (short val);

ostream& operator<< (unsigned short val);

ostream& operator<< (int val);

ostream& operator<< (unsigned int val);

ostream& operator<< (long val);

ostream& operator<< (unsigned long val);

ostream& operator<< (float val);

ostream& operator<< (double val);

ostream& operator<< (long double val);

ostream& operator<< (void* val);

ostream& operator<< (streambuf* sb );           

ostream& operator<< (ostream& (*pf)(ostream&));

ostream& operator<< (ios& (*pf)(ios&));

ostream& operator<< (ios_base& (*pf)(ios_base&));

         例子:

// example on insertion

#include      // std::cout, std::right, std::endl

#include       // std::setw

int main () {

  int val = 65;

  std::cout << std::right;       // right-adjusted (manipulator)

  std::cout << std::setw(10);    // set width (extended manipulator)

  std::cout << val << std::endl; // multiple insertions

  return 0;

}

输出:
        65

         无格式输出
         1. 把字符c插入流中

ostream& put (char c);

         例子:

// typewriter

#include      // std::cin, std::cout

#include       // std::ofstream

int main () {

  std::ofstream outfile ("test.txt");

  char ch;

  std::cout << "Type some text (type a dot to finish):\n";

  do {

    ch = std::cin.get();

    outfile.put(ch);

  } while (ch!='.');

  return 0;

}

         2. 插入字符数组s的前n个字符进入流中

ostream& write (const char* s, streamsize n);

         例子:

// Copy a file

#include       // std::ifstream, std::ofstream

int main () {

  std::ifstream infile ("test.txt",std::ifstream::binary);

  std::ofstream outfile ("new.txt",std::ofstream::binary);

  // get size of file

  infile.seekg (0,infile.end);

  long size = infile.tellg();

  infile.seekg (0);

  // allocate memory for file content

  char* buffer = new char[size];

  // read content of infile

  infile.read (buffer,size);

  // write to outfile

  outfile.write (buffer,size);

  // release dynamically-allocated memory

  delete[] buffer;

  outfile.close();

  infile.close();

  return 0;

}

         3. 定位:
         给出当前字符在输出流中的位置

streampos tellp();

         例子:

// position in output stream

#include       // std::ofstream

int main () {

  std::ofstream outfile;

  outfile.open ("test.txt");

  outfile.write ("This is an apple",16);

  long pos = outfile.tellp();

  outfile.seekp (pos-7);

  outfile.write (" sam",4);

  outfile.close();

  return 0;

}

输出:
         This is a sample

        4. 设置下一个在流中被插入字符的位置

ostream& seekp (streampos pos);     

ostream& seekp (streamoff off, ios_base::seekdir way);

         ios_base::seekdir定义:

member constant

seeking relative to

beg

beginning of sequence.

cur

current position within sequence.

end

end of sequence.

         例子:

// position in output stream

#include       // std::ofstream

int main () {

  std::ofstream outfile;

  outfile.open ("test.txt");

  outfile.write ("This is an apple",16);

  long pos = outfile.tellp();

  outfile.seekp (pos-7);

  outfile.write (" sam",4);

  outfile.close();

  return 0;

}

        同步:
         1. 同步关联的输出缓冲数据进入流

ostream& flush();

         例子:

// Flushing files

#include       // std::ofstream

int main () {

  std::ofstream outfile ("test.txt");

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

  {

    outfile << n;

    outfile.flush();

  }

  outfile.close();

  return 0;

}

1.5. 输入输出流(iostream)

        iostream从ostream和istream继承,是普通输入输出流类和用于其它输入输出流的基类。iostream的接口包括了所有的ostream和istream接口。

1.6. 输出文件流类(ofstream)

        1. 构造函数:

ofstream();

explicit ofstream (const char* filename, ios_base::openmode mode = ios_base::out);

        
         例子:

// ofstream constructor.

#include       // std::ofstream

int main () {

  std::ofstream ofs ("test.txt", std::ofstream::out);

  ofs << "lorem ipsum";

  ofs.close();

  return 0;

}

         成员函数:
         1. 打开文件

void open (const char* filename,  ios_base::openmode mode = ios_base::out);

         mode

member constant

stands for

access

in

input

File open for reading: the internal stream buffer supports input operations.

out

output

File open for writing: the internal stream buffer supports output operations.

binary

binary

Operations are performed in binary mode rather than text.

ate

at end

The output position starts at the end of the file.

app

append

All output operations happen at the end of the file, appending to its existing contents.

trunc

truncate

Any contents that existed in the file before it is open are discarded.

         插一句:app模式下,写指针每次都会在文件结尾,ate模式则只在打开时才将写指针置于文件末尾。ate赋予了程序员修改文件内容的能力。流seekp的能力是依赖于流内部缓冲区实现的。也就说如果不赋予流读文件的能力,没有读的缓冲区,流就无法seekp到文件的任意位置。所以想要定位到文件某一位置,对文件进行修改,要如下打开文件:

fstream(filename, ios::in|ios::out|ios::ate)。

         例子:

// ofstream::open / ofstream::close

#include       // std::ofstream

int main () {

  std::ofstream ofs;

  ofs.open ("test.txt", std::ofstream::out | std::ofstream::app);

  ofs << " more lorem ipsum";

  ofs.close();

  return 0;

}

         2. 确认文件是否打开

bool is_open();

        在函数内部调用rdbuf()->is_open();

         例子:

// ofstream::is_open

#include      // std::cout

#include       // std::ofstream

int main () {

  std::ofstream ofs;

  ofs.open ("test.txt");

  if (ofs.is_open())

  {

    ofs << "lorem ipsum";

    std::cout << "Output operation successfully performed\n";

    ofs.close();

  }

  else

  {

    std::cout << "Error opening file";

  }

  return 0;

}

输出:
         Output operation successfully performed

         3. 关闭当前同流关联的文件

void close();

         例子:

// ofstream::open / ofstream::close

#include       // std::ofstream

int main () {

  std::ofstream ofs;

  ofs.open ("test.txt", std::ofstream::out | std::ofstream::app);

  ofs << " more lorem ipsum";

  ofs.close();

  return 0;

}

        4. 获取内部的文件流缓冲对象的指针

filebuf* rdbuf() const;

         例子:

// copy a file using file stream buffers

#include       // std::filebuf, std::ifstream, std::ofstream

#include        // EOF

int main () {

  std::ifstream ifs ("test.txt");

  std::ofstream ofs ("copy.txt");

  std::filebuf* inbuf  = ifs.rdbuf();

  std::filebuf* outbuf = ofs.rdbuf();

  char c = inbuf->sbumpc();

  while (c != EOF)

  {

    outbuf->sputc (c);

    c = inbuf->sbumpc();

  }

  ofs.close();

  ifs.close();

  return 0;

}

1.7. 输入文件流类(ifstream)

        1. 构造函数:

ifstream();

explicit ifstream (const char* filename, ios_base::openmode mode = ios_base::in);

         例子:

// ifstream constructor.

#include      // std::cout

#include       // std::ifstream

int main () {

  std::ifstream ifs ("test.txt", std::ifstream::in);

  char c = ifs.get();

  while (ifs.good()) {

    std::cout << c;

    c = ifs.get();

  }

  ifs.close();

  return 0;

}

成员函数:

         1. 打开文件

void open (const char* filename,  ios_base::openmode mode = ios_base::in);

         例子:

// print the content of a text file.

#include      // std::cout

#include       // std::ifstream

int main () {

  std::ifstream ifs;

  ifs.open ("test.txt", std::ifstream::in);

  char c = ifs.get();

  while (ifs.good()) {

    std::cout << c;

    c = ifs.get();

  }

  ifs.close();

  return 0;

}

         2. 是否文件打开

bool is_open();

         例子:

// ifstream::is_open

#include      // std::cout

#include       // std::ifstream

int main () {

  std::ifstream ifs ("test.txt");

  if (ifs.is_open()) {

    // print file:

    char c = ifs.get();

    while (ifs.good()) {

      std::cout << c;

      c = ifs.get();

    }

  }

  else {

    // show message:

    std::cout << "Error opening file";

  }

  return 0;

}

         3. 关闭流关联的文件

void close();

         例子:

// print the content of a text file.

#include      // std::cout

#include       // std::ifstream

int main () {

  std::ifstream ifs;

  ifs.open ("test.txt");

  char c = ifs.get();

  while (ifs.good()) {

    std::cout << c;

    c = ifs.get();

  }

  ifs.close();

  return 0;

}

         4. 获取内部关联的文件缓冲区指针

filebuf* rdbuf() const;

         例子:

// read file data using associated buffer's members

#include      // std::cout

#include       // std::filebuf, std::ifstream

int main () {

  std::ifstream ifs ("test.txt", std::ifstream::binary);

  // get pointer to associated buffer object

  std::filebuf* pbuf = ifs.rdbuf();

  // get file size using buffer's members

  std::size_t size = pbuf->pubseekoff (0,ifs.end,ifs.in);

  pbuf->pubseekpos (0,ifs.in);

  // allocate memory to contain file data

  char* buffer=new char[size];

  // get file data

  pbuf->sgetn (buffer,size);

  ifs.close();

  // write content to stdout

  std::cout.write (buffer,size);

  delete[] buffer;

  return 0;

}

1.8. 输入输出文件流(fstream)

        fstream从ofstream和ifstream继承,其接口包括了所有的ofstream和ifstream接口。

1.9. 输出串流类(ostringstream)

        1. 构造函数:

explicit ostringstream (ios_base::openmode which = ios_base::out);      

explicit ostringstream (const string& str, ios_base::openmode which = ios_base::out);

         例子:

// ostringstream constructor

#include      // std::cout, std::ios

#include       // std::ostringstream

int main () {

  std::ostringstream foo;                            // out

  std::ostringstream bar (std::ostringstream::ate);  // out|ate

  foo.str("Test string");

  bar.str("Test string");

  foo << 101;

  bar << 101;

  std::cout << foo.str() << '\n';

  std::cout << bar.str() << '\n';

  return 0;

}

输出:
         101t string
         Test string101

成员函数:

         1.得到或获得字符串

string str() const;

void str (const string& s);

         例子:

// ostringstream::rdbuf

#include        // std::string

#include      // std::cout

#include       // std::ostringstream

int main () {

  std::ostringstream oss;

  oss << "One hundred and one: " << 101;

  std::string s = oss.str();

  std::cout << s << '\n';

  return 0;

}

输出:

        One hundred and one: 101

         2. 插入操作符

ostream& operator<< (bool val);

ostream& operator<< (short val);

ostream& operator<< (unsigned short val);

ostream& operator<< (int val);

ostream& operator<< (unsigned int val);

ostream& operator<< (long val);

ostream& operator<< (unsigned long val);

ostream& operator<< (float val);

ostream& operator<< (double val);

ostream& operator<< (long double val);

ostream& operator<< (void* val);

ostream& operator<< (streambuf* sb );

ostream& operator<< (ostream& (*pf)(ostream&));

ostream& operator<< (ios& (*pf)(ios&));

ostream& operator<< (ios_base& (*pf)(ios_base&));

         例子:

// example on insertion

#include      // std::cout, std::right, std::endl

#include       // std::setw

int main () {

  int val = 65;

  std::cout << std::right;       // right-adjusted (manipulator)

  std::cout << std::setw(10);    // set width (extended manipulator)

  std::cout << val << std::endl; // multiple insertions

  return 0;

}

输出:
        65

         3.插入字符c进入流

ostream& put (char c);

         例子:

// typewriter

#include      // std::cin, std::cout

#include       // std::ofstream

int main () {

  std::ofstream outfile ("test.txt");

  char ch;

  std::cout << "Type some text (type a dot to finish):\n";

  do {

    ch = std::cin.get();

    outfile.put(ch);

  } while (ch!='.');

  return 0;

}

         4.写字符串数组s的前n个字符进入流

ostream& write (const char* s, streamsize n);

         例子:

// Copy a file

#include       // std::ifstream, std::ofstream

int main () {

  std::ifstream infile ("test.txt",std::ifstream::binary);

  std::ofstream outfile ("new.txt",std::ofstream::binary);

  // get size of file

  infile.seekg (0,infile.end);

  long size = infile.tellg();

  infile.seekg (0);

  // allocate memory for file content

  char* buffer = new char[size];

  // read content of infile

  infile.read (buffer,size);

  // write to outfile

  outfile.write (buffer,size);

  // release dynamically-allocated memory

  delete[] buffer;

  outfile.close();

  infile.close();

  return 0;

}

         5.获取当前字符在流中的位置

streampos tellp();

         例子:

// position in output stream

#include       // std::ofstream

int main () {

  std::ofstream outfile;

  outfile.open ("test.txt");

  outfile.write ("This is an apple",16);

  long pos = outfile.tellp();

  outfile.seekp (pos-7);

  outfile.write (" sam",4);

  outfile.close();

  return 0;

}

         6.设置下一个插入字符在流中的位置

ostream& seekp (streampos pos);

ostream& seekp (streamoff off, ios_base::seekdir way);

         例子:

// position in output stream

#include       // std::ofstream

int main () {

  std::ofstream outfile;

  outfile.open ("test.txt");

  outfile.write ("This is an apple",16);

  long pos = outfile.tellp();

  outfile.seekp (pos-7);

  outfile.write (" sam",4);

  outfile.close();

  return 0;

}

输出:
         This is a sample

         7.同步关联的流缓冲数据进入流

ostream& flush();

         例子:

// Flushing files

#include       // std::ofstream

int main () {

  std::ofstream outfile ("test.txt");

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

  {

    outfile << n;

    outfile.flush();

  }

  outfile.close();

  return 0;

}

1.10. 输入串流类(istringstream)

         1. 构造函数:

explicit istringstream (ios_base::openmode which = ios_base::in);       

explicit istringstream (const string& str, ios_base::openmode which = ios_base::in);

          例子:

// istringstream constructors.

#include      // std::cout

#include       // std::istringstream

#include        // std::string

int main () {

  std::string stringvalues = "125 320 512 750 333";

  std::istringstream iss (stringvalues);

  for (int n=0; n<5; n++)

  {

    int val;

    iss >> val;

    std::cout << val*2 << '\n';

  }

  return 0;

}

输出:
         250
         640
         1024
         1500
         666

成员函数:
         1. 获取设置内容

string str() const;

void str (const string& s);

         例子:

// istringstream::str

#include        // std::string

#include      // std::cout

#include       // std::istringstream

int main () {

  std::istringstream iss;

  std::string strvalues = "32 240 2 1450";

  iss.str (strvalues);

  for (int n=0; n<4; n++)

  {

    int val;

    iss >> val;

    std::cout << val << '\n';

  }

  std::cout << "Finished writing the numbers in: ";

  std::cout << iss.str() << '\n';

  return 0;

}

输出:
         32
         240
         2
         1450
         Finished writing the numbers in: 32 240 21450

         2. 返回关联的内部缓冲区指针

stringbuf* rdbuf() const;

         例子:

// istringstream::rdbuf

#include        // std::string

#include      // std::cout

#include       // std::istringstream, std::stringbuf

int main () {

  std::istringstream iss;

  std::stringbuf *pbuf = iss.rdbuf();

  // using stringbuf directly:

  pbuf->str("Example string");

  int size = pbuf->in_avail();

  while (pbuf->in_avail()>0)

    std::cout << static_cast(pbuf->sbumpc());

  return 0;

}

输出:
         Example string

         3. 重载析取操作符

istream& operator>> (bool& val);

istream& operator>> (short& val);

istream& operator>> (unsigned short& val);

istream& operator>> (int& val);

istream& operator>> (unsigned int& val);

istream& operator>> (long& val);

istream& operator>> (unsigned long& val);

istream& operator>> (float& val);

istream& operator>> (double& val);

istream& operator>> (long double& val);

istream& operator>> (void*& val);

istream& operator>> (streambuf* sb );

istream& operator>> (istream& (*pf)(istream&));

istream& operator>> (ios& (*pf)(ios&));

istream& operator>> (ios_base& (*pf)(ios_base&));

         例子:

// example on extraction

#include      // std::cin, std::cout, std::hex

int main () {

  int n;

  std::cout << "Enter a number: ";

  std::cin >> n;

  std::cout << "You have entered: " << n << '\n';

  std::cout << "Enter a hexadecimal number: ";

  std::cin >> std::hex >> n;         // manipulator

  std::cout << "Its decimal equivalent is: " << n << '\n';

  return 0;

}

         4. 返回最后一次无格式输入操作所析取的字符数量

streamsize gcount() const;

         例子:

// cin.gcount example

#include      // std::cin, std::cout

int main () {

  char str[20];

  std::cout << "Please, enter a word: ";

  std::cin.getline(str,20);

  std::cout << std::cin.gcount() << " characters read: " << str << '\n';

  return 0;

}

输出:
         Please, enter a word: simplify
         9 characteres read: simplify

         5. 以无格式输入方式从输入流中析取字符

int get();

istream& get (char& c);

istream& get (char* s, streamsize n);

istream& get (char* s, streamsize n, char delim);           

istream& get (streambuf& sb);

istream& get (streambuf& sb, char delim);

         例子:

// istream::get example

#include      // std::cin, std::cout

#include       // std::ifstream

int main () {

  char str[256];

  std::cout << "Enter the name of an existing text file: ";

  std::cin.get (str,256);    // get c-string

  std::ifstream is(str);     // open file

  while (is.good())          // loop while extraction from file is possible

  {

    char c = is.get();       // get character from file

    if (is.good())

      std::cout << c;

  }

  is.close();                // close file

  return 0;

}

         6.以无格式输入方式从输入流中析取字符,并存储于c风格的字符串中。当析取字符是分割符或者已经析取了n个字符时,动作终止。默认情况下,分割符为"\n".

istream& getline (char* s, streamsize n );

istream& getline (char* s, streamsize n, char delim );

         例子:

// istream::getline example

#include      // std::cin, std::cout

int main () {

  char name[256], title[256];

  std::cout << "Please, enter your name: ";

  std::cin.getline (name,256);

  std::cout << "Please, enter your favourite movie: ";

  std::cin.getline (title,256);

  std::cout << name << "'s favourite movie is " << title;

  return 0;

}

         7. 从流中提取字符输入序列(直到n个字符提取,或遇见一个指定的字符分隔字),并丢弃。

istream& ignore (streamsize n = 1, int delim = EOF);

         例子:

// istream::ignore example

#include      // std::cin, std::cout

int main () {

  char first, last;

  std::cout << "Please, enter your first name followed by your surname: ";

  first = std::cin.get();     // get one character

  std::cin.ignore(256,' ');   // ignore until space

  last = std::cin.get();      // get one character

  std::cout << "Your initials are " << first << last << '\n';

  return 0;

}

输出:
         Please, enter your first name followed byyour surname: John Smith
         Your initials are JS

         8. 返回输入序列中的下一个字符,但不析取:字符将在下一次析取操作中从流中提取。

int peek();

        例子:

// istream::peek example

#include      // std::cin, std::cout

#include        // std::string

int main () {

  std::cout << "Please, enter a number or a word: ";

  char c = std::cin.peek();

  if ( (c >= '0') && (c <= '9') )

  {

    int n;

    std::cin >> n;

    std::cout << "You entered the number: " << n << '\n';

  }

  else

  {

    std::string str;

    std::getline (std::cin, str);

    std::cout << "You entered the word: " << str << '\n';

  }

  return 0;

}

输出:
         Please, enter a number or a word: foobar
         You entered the word: foobar

         9. 从流中析取n个字符,并把它们存储入s指向的字符数组

istream& read (char* s, streamsize n);

         例子:

// read a file into memory

#include      // std::cout

#include       // std::ifstream

int main () {

  std::ifstream is ("test.txt", std::ifstream::binary);

  if (is) {

    // get length of file:

    is.seekg (0, is.end);

    int length = is.tellg();

    is.seekg (0, is.beg);

    char * buffer = new char [length];

    std::cout << "Reading " << length << " characters… ";

    // read data as a block:

    is.read (buffer,length);

    if (is)

      std::cout << "all characters read successfully.";

    else

      std::cout << "error: only " << is.gcount() << " could be read";

    is.close();

    // …buffer contains the entire file…

    delete[] buffer;

  }

  return 0;

}

输出:
         Reading 640 characters… all characters readsuccessfully.

         10. 从流中析取n个字符,并把它们存储入s指向的字符数组。在内部关联的缓冲区(如果存在的话)用完时,操作返回,即使此时尚未达到文件结束字符。这个函数主要用于从某些类型的异步源中读取数据。由于析取操作会在内部缓冲区耗尽时终止,从而避免了潜在的延时。

streamsize readsome (char* s, streamsize n);

         11. 把指定字符还给输入流,使得字符能够被重新析取

istream& putback (char c);

         例子:

// istream::putback example

#include      // std::cin, std::cout

#include        // std::string

int main () {

  std::cout << "Please, enter a number or a word: ";

  char c = std::cin.get();

  if ( (c >= '0') && (c <= '9') )

  {

    int n;

    std::cin.putback (c);

    std::cin >> n;

    std::cout << "You entered a number: " << n << '\n';

  }

  else

  {

    std::string str;

    std::cin.putback (c);

    getline (std::cin,str);

    std::cout << "You entered a word: " << str << '\n';

  }

  return 0;

}

输出:
         Please, enter a number or a word: pocket
         You entered a word: pocket

         12. 把上一次析取的字符还给输入流,使得字符能够被重新析取

istream& unget();

         例子:

// istream::unget example

#include      // std::cin, std::cout

#include        // std::string

int main () {

  std::cout << "Please, enter a number or a word: ";

  char c = std::cin.get();

  if ( (c >= '0') && (c <= '9') )

  {

    int n;

    std::cin.unget();

    std::cin >> n;

    std::cout << "You entered a number: " << n << '\n';

  }

  else

  {

    std::string str;

    std::cin.unget();

    getline (std::cin,str);

    std::cout << "You entered a word: " << str << '\n';

  }

  return 0;

}

输出:
         Please, enter a number or a word: 7791
         You entered a number: 7791

         13. 返回当前输入流字符的位置:

streampos tellg();

         例子:

// read a file into memory

#include      // std::cout

#include       // std::ifstream

int main () {

  std::ifstream is ("test.txt", std::ifstream::binary);

  if (is) {

    // get length of file:

    is.seekg (0, is.end);

    int length = is.tellg();

    is.seekg (0, is.beg);

    // allocate memory:

    char * buffer = new char [length];

    // read data as a block:

    is.read (buffer,length);

    is.close();

    // print content:

    std::cout.write (buffer,length);

    delete[] buffer;

  }

  return 0;

}

         14. 设置下一个析取字符串在输入流中的位置

istream& seekg (streampos pos);

istream& seekg (streamoff off, ios_base::seekdir way);

         例子:

// read a file into memory

#include      // std::cout

#include       // std::ifstream

int main () {

  std::ifstream is ("test.txt", std::ifstream::binary);

  if (is) {

    // get length of file:

    is.seekg (0, is.end);

    int length = is.tellg();

    is.seekg (0, is.beg);

    // allocate memory:

    char * buffer = new char [length];

    // read data as a block:

    is.read (buffer,length);

    is.close();

    // print content:

    std::cout.write (buffer,length);

    delete[] buffer;

  }

  return 0;

}

         15. 同步流相关的缓冲区buf

int sync();

         例子:

// syncing input stream

#include      // std::cin, std::cout

int main () {

  char first, second;

  std::cout << "Please, enter a word: ";

  first = std::cin.get();

  std::cin.sync();

  std::cout << "Please, enter another word: ";

  second = std::cin.get();

  std::cout << "The first word began by " << first << '\n';

  std::cout << "The second word began by " << second << '\n';

  return 0;

}

输出:
         Please, enter a word: test
         Please enter another word: text
         The first word began by t
         The second word began by t

1.11. 输入输出串流类(stringstream)

        stringstream从iostream类继承,但是接口实现上同istringstream和ostringstream一致。

1.12. 输入输出文件流类(fstream)

        在标准库中,fstream从iostream类继承,但是接口实现上同ifstream和ofstream。采用从ifstream和ofstream多继承的方案,个人觉得实现也可以。

1.13. 流缓冲区(streambuf)

        streambuf类是所有流缓冲类的基类,内部处理窄字符(narror char)。
         缓冲区类负责关联流类的读写操作。也就说,流把所有的读写操作都委托给了缓冲区类。缓冲区类是介于流和可控的输入输出序列之间的中介。所有的流对象,不管是有缓冲的还是没有缓冲的,都关联了一个流缓冲对象。当然在流缓冲对象内部可能存在或者不存在真实的缓冲。

         本地化:
         1. 关联locale对象到缓冲区

locale pubimbue (const locale& loc);

         2. 获取当前使用的locale对象

locale getloc() const;

缓冲管理和位置信息
         1. 设置缓冲区字符数组

streambuf* pubsetbuf (char* s, streamsize n);

         例子:

// set character buffer (pubsetbuf)

#include       // std::fstream

int main () {

  char mybuffer [512];

  std::fstream filestr;

  filestr.rdbuf()->pubsetbuf(mybuffer,512);

  // operations with file stream here.

  return 0;

}

         2. 设置内部位置指针到指定位置(相对于way的偏移)

streampos pubseekoff (streamoff off, ios_base::seekdir way, ios_base::openmode which = ios_base::in | ios_base::out);

         例子:

// get file size using pubseekoff

#include      // std::cout, std::streambuf

#include       // std::fstream

int main () {

  std::fstream filestr ("test.txt");

  if (filestr) {

    std::streambuf* pbuf = filestr.rdbuf();

    long size = pbuf->pubseekoff(0,filestr.end);

    std::cout << "The file size is " << size << " characters.\n";

    filestr.close();

  }

  return 0;

}

         3. 设置内部位置指针到指定位置(绝对位置)

streampos pubseekpos (streampos pos, ios_base::openmode which = ios_base::in | ios_base::out);

         例子:

// changing position with pubseekpos

#include      // std::cout, std::streambuf

#include       // std::fstream

int main () {

  std::fstream filestr ("test.txt");

  if (filestr) {

    std::streambuf* pbuf = filestr.rdbuf();

    long size = pbuf->pubseekoff(0,filestr.end);  // get size

    if (size>20) {

      char buffer[11];

      // change position to the 10th character

      pbuf->pubseekpos(10);

      // read 10 characters

      pbuf->sgetn (buffer,10);

      // append null character to string

      buffer[10]=0;

      std::cout << buffer << '\n';

    }

    filestr.close();

  }

  return 0;

}

         4. 同步流缓冲

int pubsync();

         例子:

// pubsync member

#include      // std::cout, std::streambuf

#include       // std::ofstream

int main () {

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

  if (ostr) {

    std::streambuf * pbuf = ostr.rdbuf();

    pbuf->sputn ("First sentence\n",15);

    pbuf->pubsync();

    pbuf->sputn ("Second sentence\n",16);

    ostr.close();

  }

  return 0;

}

         输入函数:
         1. 获取可读的字符数目

streamsize in_avail();

         例子:

// get file size using pubseekoff

#include      // std::cout, std::streambuf, std::streamsize

#include       // std::ifstream

int main () {

  std::ifstream ifs ("test.txt");

  if (ifs.good()) {

    std::streambuf* pbuf = ifs.rdbuf();

    char c; ifs >> c;

    std::streamsize size = pbuf->in_avail();

    std::cout << "first character in file: " << c << '\n';

    std::cout << size << " characters in buffer after it\n";

  }

  ifs.close();

  return 0;

}

         2. 前进输入流的当前位到下一字符,并返回下一个字符

int snextc();

         例子:

// show file content - snextc() example

#include      // std::cout, std::streambuf

#include       // std::ifstream

#include        // EOF

int main () {

  std::ifstream istr ("test.txt");

  if (istr) {

    std::streambuf * pbuf = istr.rdbuf();

    do {

      char ch = pbuf->sgetc();

      std::cout << ch;

    } while ( pbuf->snextc() != EOF );

    istr.close();

  }

  return 0;

}

         3. 返回输入流的当前位字符,并前进当前位到下一个字符

int sbumpc();

         例子:

// show file content - sbumpc() example

#include      // std::cout, std::streambuf

#include       // std::ifstream

#include        // EOF

int main () {

  std::ifstream istr ("test.txt");

  if (istr) {

    std::streambuf * pbuf = istr.rdbuf();

    while ( pbuf->sgetc() != EOF )

    {

      char ch = pbuf->sbumpc();

      std::cout << ch;

    }

    istr.close();

  }

  return 0;

}

         4. 返回输入流的当前位字符,不改变位置信息

int sgetc();

         例子:

// show file content - sgetc() example

#include      // std::cout, std::streambuf

#include       // std::ifstream

#include        // EOF

int main () {

  std::ifstream istr ("test.txt");

  if (istr) {

    std::streambuf * pbuf = istr.rdbuf();

    do {

      char ch = pbuf->sgetc();

      std::cout << ch;

    } while ( pbuf->snextc() != EOF );

    istr.close();

  }

  return 0;

}

         5. 从指定的输入流中获取字符串,并把它们存储到s指向的字符串数组中。当n个字符已读或者输入流结束时,析取操作结束。

streamsize sgetn (char* s, streamsize n);

  例子:

// read a file into buffer - sgetn() example

#include      // std::cout, std::streambuf, std::streamsize

#include       // std::ifstream

int main () {

  char* contents;

  std::ifstream istr ("test.txt");

  if (istr) {

    std::streambuf * pbuf = istr.rdbuf();

    std::streamsize size = pbuf->pubseekoff(0,istr.end);

    pbuf->pubseekoff(0,istr.beg);       // rewind

    contents = new char [size];

    pbuf->sgetn (contents,size);

    istr.close();

    std::cout.write (contents,size);

  }

  return 0;

}

         6. 回退输入流的位置到指定字符c的上一个位置

int sputbackc (char c);

        例子:

// sputbackc example

#include      // std::cin, std::cout, std::streambuf, std::streamsize

#include        // EOF

int main () {

  char ch;

  std::streambuf * pbuf = std::cin.rdbuf();

  std::cout << "Please, enter some letters and then a number: ";

  do {

    ch = pbuf->sbumpc();

    if ( (ch>='0') && (ch <='9') )

    {

      pbuf->sputbackc (ch);

      long n;

      std::cin >> n;

      std::cout << "You entered number " << n << '\n';

      break;

    }

  } while ( ch != EOF );

  return 0;

}

         7. 回退输入流的位置到上一个位置,和sputbackc类似,但是不含参数c

int sungetc();

         例子:

// sputbackc example

#include      // std::cin, std::cout, std::streambuf, std::streamsize

#include        // EOF

int main () {

  char ch;

  std::streambuf * pbuf = std::cin.rdbuf();

  std::cout << "Please, enter some letters and then a number: ";

  do {

    ch = pbuf->sbumpc();

    if ( (ch>='0') && (ch <='9') )

    {

      pbuf->sputbackc (ch);

      long n;

      std::cin >> n;

      std::cout << "You entered number " << n << '\n';

      break;

    }

  } while ( ch != EOF );

  return 0;

}

输出函数:
         1. 存储字符于缓冲区当前位置并且移动输入指针位置

int sputc (char c);

         例子:

// typewriter - sputc() example

#include      // std::cin, std::cout, std::streambuf

#include       // std::ofstream

int main () {

  char ch;

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

  if (ostr) {

    std::cout << "Writing to file. Type a dot (.) to end.\n";

    std::streambuf * pbuf = ostr.rdbuf();

    do {

      ch = std::cin.get();

      pbuf->sputc(ch);

    } while (ch!='.');

    ostr.close();

  }

  return 0;

}

         2. 存储字符串序列于缓冲区中,直到存储数目到n或者输出序列结束

streamsize sputn (const char* s, streamsize n);

         例子:

// sputn() example

#include      // std::streambuf

#include       // std::ofstream

int main () {

  const char sentence[]= "Sample sentence";

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

  if (ostr) {

    std::streambuf * pbuf = ostr.rdbuf();

    pbuf->sputn (sentence,sizeof(sentence)-1);

    ostr.close();

  }

  return 0;

}

1.14. 文件缓冲区流(filebuf)

成员函数:
         1. 打开文件,并关联其内容到指定的文件缓冲

filebuf* open (const char* filename,  ios_base::openmode mode);

         例子:

// filebuf::open()

#include

#include

int main () {

  std::ifstream is;

  std::filebuf * fb = is.rdbuf();

  fb->open ("test.txt",std::ios::out|std::ios::app);

  // >> appending operations here <<

  fb->close();

  return 0;

}

         2. 判断缓冲区是否已经关联文件

bool is_open() const;

         例子:

// filebuf::is_open() example

#include

#include

int main () {

  std::ifstream is;

  std::filebuf * fb = is.rdbuf();

  fb->open ("test.txt",std::ios::in);

  if ( fb->is_open() )

    std::cout << "the file is open.\n";

  else

    std::cout << "the file is not open.\n";

  fb->close();

  return 0;

}

         3. 关闭缓冲区关联的文件,并解除关联

filebuf* close();

         例子:

// filebuf::close()

#include

#include

int main () {

  std::ifstream is;

  std::filebuf * fb = is.rdbuf();

  fb->open ("test.txt",std::ios::in);

  // appending operations

  fb->close();

  return 0;

}

1.15. 字符串的缓冲流类(stringbuf)

         1. 获取设置字符串内容

string str() const;

void str (const string& str);

         例子:

// stringbuf example

#include        // std::string

#include      // std::cout, std::ostream, std::hex

#include       // std::stringbuf

int main ()

{

  std::stringbuf buffer;             // empty buffer

  std::ostream os (&buffer);      // associate stream buffer to stream

  // mixing output to buffer with inserting to associated stream:

  buffer.sputn ("255 in hexadecimal: ",20);

  os << std::hex << 255;

  std::cout << buffer.str();

  return 0;

}

输出:

        255 in hexadecimal: ff

1.16 标准库流总结

        上面啰里啰唆的讲了半天,总结如下:

        1. ios_base封装了流的格式

        2. basic_ios封装了流的状态信息。并定义了指向streambuf的指针,也就是说:所有的流都关联一个缓冲区。在流构造时,必须显式的指定一个缓冲区streambuf的指针。文件流和字符串流在构造函数里,并没有看到streambuf指针参数,并不代表不需要,在其内部封装。所有流的输入输出操作都委托给了其内部的streambuf进行

        3. istream定义了输入操作,其中最重要的是重载了">>"操作符。

        4. ostream定义了输出操作,其中最重要的是重载了"<<"操作符。

        5. iostream多重继承于istream和ostream,能够同时完成读写功能。

        6. ofstream、ifstream、fstream完成文件读写工作,定义了文件open,close接口。

        7. ostringstream、istringstream、stringstream完成了对字符串的读写工作,定义了str接口。重载了"<<",">>"操作。

        8. cerr(无缓冲标准错误)、clog(缓冲标准错误)、cout(缓冲标准输出)、cin(行缓冲)为标准库中预定义的stream对象。它们也关联有缓冲对象,其缓冲特性如下:

            cerr(无缓冲标准错误)       -----      没有缓冲,发送给它的内容立即被输出

            clog(缓冲标准错误)          -----      有缓冲,缓冲区满时输出

            cout(缓冲标准输出)          -----     标准输出

            cin(行缓冲)

附录:

在查阅C++流的相关资料时,发现有一篇文章很有意思,重点在底下的讨论。  文章链接为《C++的流设计很糟糕》。

2. 创建自己的流

2.1 自定义流的结构

         在Poco中提供了一个框架用于创建自己的流,并且创建的流都符合C++标准。想一下标准库中流的层次和结构。每一个流都必须有对应的流缓冲,并且在流初始化时提供此流缓冲的指针。Poco中提供了3种流缓冲供选择,分别是BasicBufferedStreamBuf、BasicUnbufferedStreamBuf、BasicBufferedBidirectionalStreamBuf。首先来看一个例子:

#include "Poco/UnbufferedStreamBuf.h"

#include

#include

class UpperStreamBuf: public UnbufferedStreamBuf

{

public:

            UpperStreamBuf(std::ostream& ostr): _ostr(ostr)

            {}

protected:

            int writeToDevice(char c)

            {

                        _ostr.put(toupper(c));

                        return charToInt(c);

            }

private:

            std::ostream& _ostr;

};

class UpperIOS: public virtual std::ios

{

public:

            UpperIOS(std::ostream& ostr): _buf(ostr)

            {

                        poco_ios_init(&_buf);

            }

protected:

            UpperStreamBuf _buf;

};

class UpperOutputStream: public UpperIOS, public std::ostream

{

public:

            UpperOutputStream(std::ostream& ostr):

              UpperIOS(ostr),

                          std::ostream(&_buf)

              {}

};

int main(int argc, char** argv)

{

            UpperOutputStream upper(std::cout);

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

            return 0;

}

          从上面的例子中,我们看到用户自定流缓冲类型UpperStreamBuf,并自定义流类型UpperIOS和UpperOutputStream。其中UpperIOS从std::ios虚拟继承。注意这里是虚拟继承,这是为了保证UpperOutputStream多重继承时的菱形结构(std::ostream也是从std::ios虚拟继承的)。再回忆一下,std::ios的作用,其中最重要的就是定义了缓冲区指针。那么很自然的,定义UpperIOS,并让UpperOutputStream从UpperIOS继承的原因,就是为了让UpperOutputStream类拥有自己的缓冲区。再来看从流std::ostream继承的原因,很简单,为了继承"<<"操作符。那么清楚了,如果是输入流,从std::istream继承;是输入输出流从std::iostream继承。

          再从上面的例子看:为什么说,Poco中流框架提供的是一个中介流的框架。当创建UpperOutputStream对象时,我们看它做了什么.
          在调用UpperOutputStreamupper(std::cout)时,传入参数是一个std::cout,这是一个已定义的流对象。这个流对象被赋予给UpperIOS类,用来初始化UpperIOS类中的UpperStreamBuf对象;而UpperStreamBuf又并不负责直接输出,它的输出依赖于内部的std::ostream对象,在这里就是std::cout。另一方面由于UpperOutputStream对象同时也继承自std::ostream,而std::ostream必须要求用一个流缓冲对象来初始化,所以在UpperIOS类中的UpperStreamBuf对象初始化后,又被赋给std::ostream对象用作初始化。

          再来看 "upper <<"Hello, world!" << std::endl "语句的执行过程:
          1. "<<"操作符是std::ostream的成员函数。也就是说 "Hello,world!"被首先调用了std::ostream函数"<<"。

         2.  我们知道,流的输入输出是委托给其内部的缓冲区对象的。也就是说,在这里"<<"操作符,会委托到std::ostream类内部关联的流缓冲UpperIOS::UpperStreamBuf对象_buf上。
          2. UpperStreamBuf类型的_buf对象并不输出,它把输出任务继续委托给内部的流对象_ostr。在程序里,_ost对象为std::cout。
          3. std::cout对象在收到数据后,委托给std::cout内部关联的流缓冲对象,最终输出。至于std::cout对象关联的流对象是什么,那就不用去管了。这是系统内部实现的。

          转了半天,输出最终还是依赖于系统的默认输出。如果仅是如此,就没有意思了。Poco流在类BasicBufferedStreamBuf、BasicUnbufferedStreamBuf、BasicBufferedBidirectionalStreamBuf上开了一个小口,供用户自定义中间操作。这两个小口就是:

virtual int readFromDevice(char_type* buffer, std::streamsize length);

virtual int writeToDevice(const char_type* buffer, std::streamsize length);

          通过子类继承,用户可以自定义转换行为。任何从Poco流自定义框架出来的类都必须重新定义这两个虚函数。readFromDevice和writeToDevice会在std::ostream中的虚函数overflow和underflow中被调用。

          在了解了Poco自定义流类的流转次序后,再去看Poco中的自定义流Base64Decoder,Base64Encoder等的数据流转,会发现过程都是类似的。下面就不再对这一方面进行叙述。只是讲一下Poco中已定义的3个流缓冲类BasicBufferedStreamBuf、BasicUnbufferedStreamBuf、BasicBufferedBidirectionalStreamBuf。

2.2 BasicUnbufferedStreamBuf

         Poco::BasicUnbufferedStreamBuf是一个使用char作为模板参数类型的偏特化模板类。其完整定义为:

template

class BasicUnbufferedStreamBuf: public std::basic_streambuf

            /// This is an implementation of an unbuffered streambuf

            /// that greatly simplifies the implementation of

            /// custom streambufs of various kinds.

            /// Derived classes only have to override the methods

            /// readFromDevice() or writeToDevice().

{

            // …..

            int_type _pb;

            bool     _ispb;

}

typedef BasicUnbufferedStreamBuf > UnbufferedStreamBuf;

          Poco::BasicUnbufferedStreamBuf可以说是最简单的创建用户自定流的方法,在其内部没有任何缓冲对象。从Poco::BasicUnbufferedStreamBuf继承的类需要重载readFromDevice和writeToDevice接口。

          对应重载的要求如下:

     int readFromDevice()

         读取并返回单字节无符号字符。如果没有更多的合适数据时,返回char_traits::eof()(-1)。需要注意的是,不要直接返回一个char类型值,因为char类型值是一个有符号数。调用intcharToInt(char c)返回一个char转换到int的值。

     int writeToDevice(char c)

         写入一个单字节字符,如果成功返回写入的字符(已整形方式),否则返回char_traits::eof() (-1).

          在Poco中从UnbufferedStreamBuf继承的类见下图:

2.3 BasicBufferedStreamBuf

         Poco::BasicBufferedStreamBuf也同样是使用char作为模板参数类型的偏特化模板类。其完整定义为:

template >

class BasicBufferedStreamBuf: public std::basic_streambuf

            /// This is an implementation of a buffered streambuf

            /// that greatly simplifies the implementation of

            /// custom streambufs of various kinds.

            /// Derived classes only have to override the methods

            /// readFromDevice() or writeToDevice().

            ///

            /// This streambuf only supports unidirectional streams.

            /// In other words, the BasicBufferedStreamBuf can be

            /// used for the implementation of an istream or an

            /// ostream, but not for an iostream.

{

            // …

            std::streamsize _bufsize;

            char_type*      _pBuffer;

            openmode        _mode;

}

typedef BasicBufferedStreamBuf > BufferedStreamBuf;

          在BasicBufferedStreamBuf内部存在一个_pBuffer,用作缓冲,供输出使用。Poco::BufferedStreamBuf的实例支持读写,但不支持同时读写。从Poco::BasicBufferedStreamBuf继承的类需要重载readFromDevice和writeToDevice接口。

          对应重载的要求如下:  

     int readFromDevice(char* buffer, std::streamsize length)

         读取指定长度的字符,并把它们放置在buffer中。返回读到的字节数,或者-1(当错误发生时)

     int writeToDevice(const char* buffer, std::streamsize length)

         写入buffer中指定数目的字节。返回读到的字节数,或者-1(当错误发生时)

          在Poco中从BasicBufferedStreamBuf继承的类见下图:

2.4BasicBufferedBidirectionalStreamBuf

         Poco::BasicBufferedBidirectionalStreamBuf一样是使用char作为模板参数类型的偏特化模板类。其完整定义为:

template >

class BasicBufferedBidirectionalStreamBuf: public std::basic_streambuf

            /// This is an implementation of a buffered bidirectional

            /// streambuf that greatly simplifies the implementation of

            /// custom streambufs of various kinds.

            /// Derived classes only have to override the methods

            /// readFromDevice() or writeToDevice().

            ///

            /// In contrast to BasicBufferedStreambuf, this class supports

            /// simultaneous read and write access, so in addition to

            /// istream and ostream this streambuf can also be used

            /// for implementing an iostream.

{

            // ….

            std::streamsize _bufsize;

            char_type*      _pReadBuffer;

            char_type*      _pWriteBuffer;

            openmode        _mode;

}

typedef BasicBufferedBidirectionalStreamBuf > BufferedBidirectionalStreamBuf;

          在BasicBufferedBidirectionalStreamBuf内部存在两个缓冲,_pReadBuffer和_pWriteBuffer,作为缓冲,分别供输入或输出使用。
          Poco::BasicBufferedBidirectionalStreamBuf的实例支持同时读写。从Poco::BasicBufferedBidirectionalStreamBuf继承的类需要重载readFromDevice和writeToDevice接口。

          对应重载的要求如下:

     int readFromDevice(char* buffer, std::streamsize length)

         读取指定长度的字符,并把它们放置在buffer中。返回读到的字节数,或者-1(当错误发生时)

     int writeToDevice(const char* buffer, std::streamsize length)

         写入buffer中指定数目的字节。返回读到的字节数,或者-1(当错误发生时)

          在Poco中从BasicBufferedBidirectionalStreamBuf继承的类见下图:

3. Poco::Base64Encoder和Poco::Base64Decoder

3.1 Base64编码的概念

         关于Base64编码的概念的概念出自wiki百科。想要得到更加详细的资料,可以去看RFC 4648

         Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个位元为一个单元,对应某个可打印字符。三个字节有24个位元,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。它可用来作为电子邮件的传输编码。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9 ,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。
          Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据。包括MIME的email,emailvia MIME, 在XML中存储复杂数据.

                      Base64索引表:

Value

Char

Value

Char

Value

Char

Value

Char

0

A

16

Q

32

g

48

w

1

B

17

R

33

h

49

x

2

C

18

S

34

i

50

y

3

D

19

T

35

j

51

z

4

E

20

U

36

k

52

0

5

F

21

V

37

l

53

1

6

G

22

W

38

m

54

2

7

H

23

X

39

n

55

3

8

I

24

Y

40

o

56

4

9

J

25

Z

41

p

57

5

10

K

26

a

42

q

58

6

11

L

27

b

43

r

59

7

12

M

28

c

44

s

60

8

13

N

29

d

45

t

61

9

14

O

30

e

46

u

62

+

15

P

31

f

47

v

63

/

    Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个位元为一个单元,对应某个可打印字符。三个字节有24个位元,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。它可用来作为电子邮件的传输编码。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9 ,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。

文本(1 Byte)

A

二进制位

0

1

0

0

0

0

0

1

二进制位(补0)

0

1

0

0

0

0

0

1

0

0

0

0

Base64编码

Q

Q

文本(2 Byte)

B

C

二进制位

0

1

0

0

0

0

1

0

0

1

0

0

0

0

1

1

x

x

x

x

x

x

二进制位(补0)

0

1

0

0

0

0

1

0

0

1

0

0

0

0

1

1

0

0

x

x

x

x

x

x

Base64编码

Q

k

M

例子:
          举例来说,一段引用自托马斯·霍布斯的利维坦的文句:

Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.

          经过base64编码之后变成:

TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz

IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg

dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu

dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo

ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=

          编码“Man”

文本

M

a

n

ASCII编码

77

97

110

二进制位

0

1

0

0

1

1

0

1

0

1

1

0

0

0

0

1

0

1

1

0

1

1

1

0

索引

19

22

5

46

Base64编码

T

W

F

u

         在此例中,Base64算法将三个字符编码为4个字符

         基本上说,如果那天你看到一段以“=”或者"=="结束的字符,你就可以判断这段字符用了Base64编码。就应用而言,我只在邮件内容发送和MD5 或者SHA密码保存时见过Base64编码。

        实现上Base64算法很简单,把流变成二进制,然后按6位转换为字符保存。

3.2 Poco::Base64Encode

         Poco::Base64Encode的类图如下:

3.3 Poco::Base64Decoder

         Poco::Base64Decoder的类图如下:

3.4 例子

         下面是一个例子

#include "Poco/Base64Encoder.h"

#include

using Poco::Base64Encoder;

int main(int argc, char** argv)

{

       Base64Encoder encoder(std::cout);

       encoder << "Hello, world!";

       return 0;

}

4. Poco::HexBinaryEncoder和Poco::HexBinaryDecoder

4.1 HexBinary编码编码概念

         HexBinary编码又叫Base 16编码,Base16是一种基于16个可打印字符来表示二进制数据的表示方法。由于2的4次方等于16,所以每4个位元为一个单元,对应某个可打印字符。三个字节有24个位元,对应于6个Base64单元,即3个字节需要用6个可打印字符来表示。

Base16索引表:

                               The Base 16 Alphabet

         Value Encoding  Value Encoding  Value Encoding  Value Encoding

             0 0             4 4             8 8            12 C

             1 1             5 5             9 9            13 D

             2 2             6 6            10 A            14 E

             3 3             7 7            11 B            15 F

          还是以编码“Man”举例:

文本

M

a

n

ASCII编码

                    77

                        97

                         110

二进制位

0

1

0

0

1

1

0

1

0

1

1

0

0

0

0

1

0

1

1

0

1

1

1

0

索引

          4

         13

             6

           1 

             6

          14

base 16编码

4

D

6

1

6

E

        实现上Base16算法很简单,把流变成二进制,然后按4位转换为字符保存。

4.2 Poco::HexBinaryEncoder和Poco::HexBinaryDecoder

     Poco::HexBinaryEncoder 和Poco::HexBinaryDecoder的类图同Poco::Base64Encoder和Poco::Base64Decoder是类似。

      HexBinaryDecoderBuf定义如下:

class Foundation_API HexBinaryDecoderBuf: public UnbufferedStreamBuf

            /// This streambuf decodes all hexBinary-encoded data read

            /// from the istream connected to it.

            /// In hexBinary encoding, each binary octet is encoded as a character tuple, 

            /// consisting of two hexadecimal digits ([0-9a-fA-F]) representing the octet code.

            /// See also: XML Schema Part 2: Datatypes (http://www.w3.org/TR/xmlschema-2/),

            /// section 3.2.15.

            ///

            /// Note: For performance reasons, the characters

            /// are read directly from the given istream's

            /// underlying streambuf, so the state

            /// of the istream will not reflect that of

            /// its streambuf.

{

public:

            HexBinaryDecoderBuf(std::istream& istr);

            ~HexBinaryDecoderBuf();

private:

            int readFromDevice();

            int readOne();

            std::streambuf& _buf;

};

      构造文件为:

HexBinaryDecoderBuf::HexBinaryDecoderBuf(std::istream& istr):

            _buf(*istr.rdbuf())

{

}

      我们看到 HexBinaryDecoderBuf直接使用了istream对象内部的缓冲作为实现。同2.1节中的例子相比,可以减少函数调用的流程.

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

5 . ZLib Stream流

           Poco::DeflatingInputStream、Poco::DeflatingOutputStream、Poco::InflatingInputStream和Poco::InflatingOutputStream把zlib的压缩过程封装成为流方式。在讨论其实现之前,先来了解一下涉及到的压缩和校验算法。

5.1 zlib库

          在Poco中实现的压缩过程是通过zlib库实现的。下面对zlib的介绍主要来自于wiki百科
           zlib是提供资料压缩之用的函式库,由Jean-loup Gailly与MarkAdler所开发,初版0.9版在1995年5月1日发表。
           zlib目前应用很广泛,下面是其一些应用例子:
                 * Linux核心:使用zlib以实作网络协定的压缩、档案系统的压缩以及开机时解压缩自身的核心。
                 * libpng,用于PNG图形格式的一个实现,对bitmap数据规定了DEFLATE作为流压缩方法。
                 * Apache:使用zlib实作http1.1。
                 * OpenSSH、OpenSSL:以zlib达到最佳化加密网络传输。
                 * FFmpeg:以zlib读写Matroska等以DEFLATE算法压缩的多媒体串流格式。
                 * rsync:以zlib最佳化远端同步时的传输。
                 * The dpkg andRPM package managers, which use zlib to unpack files from compressed softwarepackages.
                 * Subversion 、Git和CVS 版本控制系统,使用zlib来压缩和远端仓库的通讯流量。
                 * dpkg和RPM等包管理软件:以zlib解压缩RPM或者其他封包。
                * 因为其代码的可移植性,宽松的许可以及较小的内存占用,zlib在许多嵌入式设备中也有应用。

           zlib支持两种封装格式:gzip和zlibstream。二者都是对deflate压缩算法的封装。HTTP1.1(rfc2616)里的gzip就代表gzip,而deflate则代表zlib stream,并非raw deflate。(这里raw deflate是指不加头的使用deflate算法压缩的原始数据)。下面是gzip和zlib的格式示意。

          按照gzip的RFC 1952,gzip压缩格式包含如下部分:

                    +---+---+---+---+---+---+---+---+---+---+

                    |ID1|ID2|CM |FLG|     MTIME     |XFL|OS | (more-->)

                    +---+---+---+---+---+---+---+---+---+---+

                    +=======================+

                    |…compressed blocks…| (more-->)

                    +=======================+

                      0   1   2   3   4   5   6   7

                    +---+---+---+---+---+---+---+---+

                    |     CRC32     |     ISIZE     |

                    +---+---+---+---+---+---+---+---+

           zlib的格式大致如下:

                    +----+-----+

                    |CMF|FLG|    (more-->)

                    +----+-----+

                    (if FLG.FDICT set)

                    0     1     2     3

                    +-----+-----+-----+-----+

                    |       DICTID          | (more-->)

                    +-----+-----+-----+-----+

                    +========================

                    | …compressed data… |

                    +========================

                    +-----+-----+-----+-----+

                    |         ADLER32       |

                    +-----+-----+-----+-----+

           注:
                    在上面的图中,像这样的框,代表一个字节

                    +---+

                    |   |  <-- 垂直的边框可能会丢掉

                    +---+

                   像这样的框,代表可变数目的字节。

                    +==============+

                    |              |

                    +==============+

           比较上面的两张图,可以看出在zlib库支持的两种格式之间只有头和尾不同而已。中间的"compressed data"数据段就是所谓的raw deflate。

          raw deflate数据段使用deflate算法压缩的,而defalte算法是LZ77 压缩算法和霍夫曼编码压缩的结合。有点绕,下面把涉及到的名词列一下:
                LZ77: 是一种基于字典的无损数据压缩算法(还有 LZ78, LZW 等)
                deflate: 也是一种数据压缩算法,实际上就是先用 LZ77 压缩,然后用霍夫曼编码压缩
                gzip: 是一种文件结构,也可以算一种压缩格式,通过 defalte 算法压缩数据,然后加上文件头和adler32校验
                zlib: 是一个提供了deflate, zlib, gzip 压缩方法的函数库;也是一种压缩格式(用 deflate 压缩数据,然后加上 zlib 头和 CRC 校验)

5.2 LZ77算法

          LZ77算法是一种基于字典的压缩算法,并且是基于动态字典的压缩算法。既然存在动态词典,那么就一定也存在静态词典。先来看定义:
           静态字典:预先定义好的字典。
           动态字典:采用之前的数据作为字典。

           那二者之间的区别又在什么地方呢?
           1. 静态字典字典本身就是一个数据量很大的数据块,在压缩出来的数据中必须维护这样一个数据块,很大程度上会影响到压缩率。而动态压缩则不同,由于动态压缩的字典是基于已经压缩好的数据的,因此动态压缩不需要另外维护一个字典,这样就可以一定程度上保证压缩率。
           2. 静态字典都是已经预定义好的,因此针对不同数据我们需要维护不同的字典,但是动态字典则不同,动态字典是自适应的,因此不需要去维护不同的字典,更适合我们的需求。
         
  LZ77算法又称之为“滑动窗口压缩”,因为该算法在原始数据上虚拟了一个滑动的窗口,这个窗口中的所有元素序列就构成了一个字典,由于窗口是滑动的,因此辞典也是实时变化的,所以这就是一个典型的动态辞典。

  LZ77压缩过程如下图:

         大致过程如下:
         1. 从当前压缩位置开始,考察未编码的数据,并试图在滑动窗口中找出最长的匹配字符串,如果找到,则进行步骤 2,否则进行步骤 3。
         2. 输出三元符号组 ( off, len, c )。其中off 为窗口中匹配字符串相对窗口边界的偏移,len 为可匹配的长度,c 为下一个字符。然后将窗口向后滑动 len + 1 个字符,继续步骤 1。
         3. 输出三元符号组 ( 0, 0, c )。其中c 为下一个字符。然后将窗口向后滑动 len + 1 个字符,继续步骤 1。
         这样,我们将可以匹配的字符串都变成了指向窗口内的指针,并由此完成了对上述数据的压缩。

  使用LZ77算法的解压缩的过程十分简单,只要我们向压缩时那样维护好滑动的窗口,随着三元组的不断输入,我们在窗口中找到相应的匹配串,缀上后继字符c 输出(如果 off 和 len 都为0 则只输出后继字符 c )即可还原出原始数据。

        举个例子,考虑这样一句话:

        the brown fox jumped over the brown foxy jumping frog

        这个短语的长度总共是53个八位组 =424 bit。算法从左向右处理这个文本。初始时,每个字符被映射成9 bit的编码,二进制的1跟着该字符的8bit ASCII码。在处理进行时,算法查找重复的序列。当碰到一个重复时,算法继续扫描直到该重复序列终止。换句话说,每次出现一个重复时,算法包括尽可能多的字符。碰到的第一个这样的序列是the brown fox。这个序列被替换成指向前一个序列的指针和序列的长度。在这种情况下,前一个序列的the brown fox出现在26个字符之前,序列的长度是13个字符。对于这个例子,假定存在两种编码选项:8 bit的指针和4 bit的长度,或者12 bit的指针和6 bit的长度。使用2 bit的首部来指示选择了哪种选项,00表示第一种选项,01表示第二种选项。因此,the brown fox的第二次出现被编码为 <00b><26d><13 d >,或者00000110101101。

         压缩报文的剩余部分是字母y;序列<00b><27d><5 d >替换了由一个空格跟着jump组成的序列,以及字符序列ing frog。

         下图演示了压缩映射的过程。压缩过的报文由35个9bit字符和两个编码组成,总长度为35 x 9 + 2 x 14 = 343比特。和原来未压缩的长度为424比特的报文相比,压缩比为1.24。

5.2.1 压缩算法的说明

          LZ77(及其变体)的压缩算法使用了两个缓存。滑动历史缓存包含了前面处理过的N个源字符,前向缓存包含了将要处理的下面L个字符(如下图)。算法尝试将前向缓存开始的两个或多个字符与滑动历史缓存中的字符串相匹配。如果没有发现匹配,前向缓存的第一个字符作为9 bit的字符输出并且移入滑动窗口,滑动窗口中最久的字符被移出。如果找到匹配,算法继续扫描以找出最长的匹配。然后匹配字符串作为三元组输出(指示标记、指针和长度)。对于K个字符的字符串,滑动窗口中最久的K个字符被移出,并且被编码的K个字符被移入窗口。

         下图显示了这种模式对于我们的例子的运行情况。这里假定了39个字符的滑动窗口和13个字符的前向缓存。在这个例子的上半部分,已经处理了前面的40个字符,滑动窗口中是未压缩的最近的39个字符。剩下的源字符串在前向窗口中。压缩算法确定了下一个匹配,从前向窗口将5个字符移入到滑动窗口中,并且输出了这个匹配字符串的编码。经过这些操作的缓存的状态显示在这个例子的下半部分。

         尽管LZ77是有效的,对于当前的输入情况也是合适的,但是存在一些不足。算法使用了有限的窗口在以前的文本中查找匹配,对于相对于窗口大小来说非常长的文本块,很多可能的匹配就会被丢掉。窗口大小可以增加,但这会带来两个损失:

        (1)算法的处理时间会增加,因为它必须为滑动窗口的每个位置进行一次与前向缓存的字符串匹配的工作;

        (2)<指针>字段必须更长,以允许更长的跳转。

5.2.2 编码方法

        我们必须精心设计三元组中每个分量的表示方法,才能达到较好的压缩效果。一般来讲,编码的设计要根据待编码的数值的分布情况而定。

        对于三元组的第一个分量——窗口内的偏移,通常的经验是,偏移接近窗口尾部的情况要多于接近窗口头部的情况,这是因为字符串在与其接近的位置较容易找到匹配串,但对于普通的窗口大小(例如 4096 字节)来说,偏移值基本还是均匀分布的,我们完全可以用固定的位数来表示它。
         编码 off 需要的位数 bitnum = upper_bound( log2(MAX_WND_SIZE ))
         由此,如果窗口大小为 4096,用 12 位就可以对偏移编码。如果窗口大小为 2048,用 11 位就可以了。复杂一点的程序考虑到在压缩开始时,窗口大小并没有达到MAX_WND_SIZE,而是随着压缩的进行增长,因此可以根据窗口的当前大小动态计算所需要的位数,这样可以略微节省一点空间。
         对于第二个分量——字符串长度,我们必须考虑到,它在大多数时候不会太大,少数情况下才会发生大字符串的匹配。显然可以使用一种变长的编码方式来表示该长度值。在前面我们已经知道,要输出变长的编码,该编码必须满足前缀编码的条件。其实 Huffman 编码也可以在此处使用,但却不是最好的选择。比较常用的是γ 编码和Golomb编码。
         对三元组的最后一个分量——字符 c,因为其分布并无规律可循,我们只能老老实实地用 8 个二进制位对其编码。

5.3 霍夫曼编码

        霍夫曼(Huffman)编码属于码词长度可变的编码类,是霍夫曼在1952年提出的一种编码方法,即从下到上的编码方法。同其他码词长度可变的编码一样,可区别的不同码词的生成是基于不同符号出现的不同概率。生成霍夫曼编码算法基于一种称为“编码树”(coding tree)的技术。算法步骤如下:
         (1)初始化,根据符号概率的大小按由大到小顺序对符号进行排序。
         (2)把概率最小的两个符号组成一个新符号(节点),即新符号的概率等于这两个符号概率之和。
         (3)重复第2步,直到形成一个符号为止(树),其概率最后等于1。
         (4)从编码树的根开始回溯到原始的符号,并将每一下分枝赋值为1,上分枝赋值为0。
         以下这个简单例子说明了这一过程。
         1).字母A,B,C,D,E已被编码,相应的出现概率如下:
              p(A)=0.16, p(B)=0.51,p(C)=0.09, p(D)=0.13, p(E)=0.11
         2).C和E概率最小,被排在第一棵二叉树中作为树叶。它们的根节点CE的组合概率为0.20。从CE到C的一边被标记为1,从CE到E的一边被标记为0。这种标记是强制性的。所以,不同的哈夫曼编码可能由相同的数据产生。
         3).各节点相应的概率如下:
             p(A)=0.16, p(B)=0.51,p(CE)=0.20, p(D)=0.13
             D和A两个节点的概率最小。这两个节点作为叶子组合成一棵新的二叉树。根节点AD的组合概率为0.29。由AD到A的一边标记为1,由AD到D的一边标记为0。
             如果不同的二叉树的根节点有相同的概率,那么具有从根到节点最短的最大路径的二叉树应先生成。这样能保持编码的长度基本稳定。
         4).剩下节点的概率如下:
            p(AD)=0.29, p(B)=0.51,p(CE)=0.20
            AD和CE两节点的概率最小。它们生成一棵二叉树。其根节点ADCE的组合概率为0.49。由ADCE到AD一边标记为0,由ADCE到CE的一边标记为1。
         5).剩下两个节点相应的概率如下:
            p(ADCE)=0.49, p(B)=0.51
            它们生成最后一棵根节点为ADCEB的二叉树。由ADCEB到B的一边记为1,由ADCEB到ADCE的一边记为0。
         6).下图为霍夫曼编码。编码结果被存放在一个表中:
             w(A)=001, w(B)=1, w(C)=011,w(D)=000, w(E)=010

5.4 CRC循环冗余校验

        循环冗余校验(英语:Cyclic redundancy check,通称“CRC”)是一种根据网络数据数据包或电脑文件等数据产生简短固定位数校验码的一种散列函數,主要用来检测或校验数据传输或者保存后可能出现的错误。生成的数字在传输或者存储之前计算出来并且附加到数据后面,然后接收方进行检验确定数据是否发生变化。一般来说,循环冗余校验的值都是32位的整数。由于本函数易于用二进制的电脑硬件使用、容易进行数学分析并且尤其善于检测传输通道干扰引起的错误,因此获得广泛应用。具体算法可以看<循环冗余校验(CRC)算法入门引导>

5.5 ADLER32校验

        Adler-32通过求解两个16位的数值A、B实现,并将结果连结成一个32位整数。其算法描述如下:
                  A = 1 + D1+ D2 + … + Dn (mod 65521)
                  B = (1 +D1) + (1 + D1 + D2) + … + (1 + D1 + D2 + … + Dn) (mod 65521) = n×D1 +(n-1)×D2 + (n-2)×D3 + … + Dn + n (mod 65521)
                  Adler-32(D)= B × 65536 + A

        举个例子:
         ASCII code          A                                     B
         (10进制)  
            W :  87          1 +  87 =  88             0 +  88 =   88
            i   :  105        88 + 105 = 193         88 + 193 = 281
            k  :  107        193 + 107 = 300       281 + 300 =  581
            i   :  105        300 + 105 = 405       581 + 405 =  986
            p  :  112        405 + 112 = 517       986 + 517 = 1503
            e  :  101        517 + 101 = 618       1503 + 618 = 2121
            d  :  100        618 + 100 = 718       2121 + 718 = 2839
            i   :  105        718 + 105 = 823       2839 + 823 = 3662
            a  :   97         823 +  97 = 920        3662+ 920 = 4582

         经过上面的一轮运算:
          A = 920  
          B = 4582
          Adler-32(D)  = B × 65536 + A = 4582×65536 +920 = 300286872

          300286872 转化成十六进制 11E60398(此数即是输出的结果)。

5.6 Zlib流类

         在Poco库中,存在两个流类用来把原始数据转换成为压缩数据,分别是Poco::InflatingInputStream和Poco::InflatingOutputStream。这两个类通过另外一个输入、输出流构建,并可以指定数据的压缩格式。

Poco::DeflatingStreamBuf::STREAM_ZLIB (deflate/zlib 格式)

Poco::DeflatingStreamBuf::STREAM_GZIP (gzip 格式)

          而Poco::InflatingInputStream类和Poco::InflatingOutputStream类,用来把压缩数据还原成为原始数据。同样的它们也是通过另外一个输入、输出流构建,并可以指定数据的压缩格式。

Poco::InflatingStreamBuf::STREAM_ZLIB (deflate/zlib 格式)

Poco::InflatingStreamBuf::STREAM_GZIP (gzip 格式)

          原始数据和压缩数据之间的转换过程可见下图:

         下面来看一个例子:

#include "Poco/DeflatingStream.h"

#include "Poco/InflatingStream.h"

#include

using Poco::DeflatingOutputStream;

using Poco::DeflatingStreamBuf;

using Poco::InflatingInputStream;

using Poco::InflatingStreamBuf;

int main(int argc, char** argv)

{

     std::ofstream ostr("test.gz", std::ios::binary);

     DeflatingOutputStream deflater(ostr, DeflatingStreamBuf::STREAM_GZIP);

     deflater << "Hello, world! ads \n" << "12306";

     // ensure buffers get flushed before connected stream is closed

     deflater.close();

     ostr.close();

     std::ifstream istr("test.gz", std::ifstream::in);

     InflatingInputStream infalter(istr ,InflatingStreamBuf::STREAM_GZIP);

     string str;

     while (infalter.good())          // loop while extraction from file is possible 

     { 

          char c = infalter.get();    // get character from file 

          str.append(1, c);

     }

     return 0;

}

          在上面那个例子里,使用析取操作符">>",会使空格和换行符丢失,所以只能逐一获取字符。

6. Poco::CountingInputStream 和Poco::CountingOutputStream

         Poco::CountingInputStream和Poco::CountingOutputStream类可以用来计算文件中的字符数目和行数,同时它们也可以提供当前所在行和列数。下面是一个例子:

#include "Poco/CountingStream.h"

#include

using Poco::CountingInputStream;

using Poco::CountingOutputStream;

int _tmain(int argc, _TCHAR* argv[])

{

            char c;

            std::istringstream istr1("foo");

            CountingInputStream ci1(istr1);

            while (ci1.good()) ci1.get(c);

            assert (ci1.lines() == 1);

            assert (ci1.chars() == 3);

            assert (ci1.pos() == 3);

            std::ostringstream ostr2;

            CountingOutputStream co2(ostr2);

            co2 << "foo\nbar";

            assert (ostr2.str() == "foo\nbar");

            assert (co2.lines() == 2);

            assert (co2.chars() == 7);

            assert (co2.pos() == 3);

            return 0;

}

7. Poco::InputLineEndingConverter和Poco::OutputLineEndingConverter

         Poco::InputLineEndingConverter和Poco::OutputLineEndingConverter用来转换不同操作系统上的文本文件的行结尾(Unix (LF)、DOS/Windows (CRLF)、Macintosh (CR))。下面是一个例子:

#include "Poco/LineEndingConverter.h"

#include "Poco/StreamCopier.h"

#include

using Poco::LineEnding;

using Poco::InputLineEndingConverter;

using Poco::OutputLineEndingConverter;

using Poco::StreamCopier;

int _tmain(int argc, _TCHAR* argv[])

{

            std::istringstream input("line1\r\nline2\r\nline3\r\n");

            std::ostringstream output;

            InputLineEndingConverter conv(input, LineEnding::NEWLINE_LF);

            StreamCopier::copyStream(conv, output);

            std::string result = output.str();

            assert (result == "line1\nline2\nline3\n");

            return 0;

}

8. Poco::TeeInputStream 和Poco::TeeOutputStream

         Poco::TeeInputStream和Poco::TeeOutputStream流会拷贝所有的输入和输出字符串,并把它们输出到一个或多个流上。这两个类主要被用于调试情况下。

#include "Poco/TeeStream.h"

#include

#include

using Poco::TeeOutputStream;

int main(int argc, char** argv)

{

            TeeOutputStream tee(std::cout);

            std::ofstream fstr("output.txt");

            tee.addStream(fstr);

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

            return 0;

}

9. Poco::NullOutputStream

         Poco::NullOutputStream类会抛弃所有对它的写操作,对所有的读操作会返回文件结束符。下面是一个例子:

#include

#include "Poco/NullStream.h"

using Poco::NullInputStream;

using Poco::NullOutputStream;

int main(int argc, char** argv)

{

            NullInputStream istr;

            assert (istr.good());

            assert (!istr.eof());

            int c = istr.get();

            assert (c == -1);

            assert (istr.eof());

            return 0;

}

10. Poco::BinaryWriter和Poco::BinaryReader

10. 1 Poco::BinaryWriter

            Poco::BinaryWriter被用作向一个输出流中写入基础数据类型的二进制形式。Poco::BinaryReader被用作从一个保存着Poco::BinaryWriter类存储的二进制形式的内容的输入流中读取基础数据类型的数据。
            Poco::BinaryReader和Poco::BinaryWriter类都支持大小字节序的数据读写,并且能够在内部转换所需的字节序。这两个类主要用于不同架构的操作系统之间的数据转换。

            Poco::BinaryWriter支持所有c++内建数据类型、c风格的字符串和std::string的插入操作符"<<"。在Poco::BinaryWriter类中无符号整型(包括32和64位)的存储是以一种特殊的7位编码方式进行的。

            成员函数:
            1.void write7BitEncoded(UInt32value)
                voidwrite7BitEncoded(UInt64 value)
              向底层输出流以简洁7位编码方式写入一个无符号整形
            2. void writeRaw(conststd::string& rawData)
              向底层输出流写入裸数据
            3. void writeBOM()
              向流中写入一个字节序标志BOM(byteorder mark)(一个16位值0xFEFF的本机序)。如果需要的话,BinaryReader会使用BOM标志自动进行字节序转换

           BinaryWriter类和字节序:
            BinaryWriter类通过一个输出流和一个字节序标志进行构造。字节序标志有如下选择:
              NATIVE_BYTE_ORDER          (本机序)
              BIG_ENDIAN_BYTE_ORDER     (大头序)
              NETWORK_BYTE_ORDER         (网络序)
              LITTLE_ENDIAN_BYTE_ORDER   (小头序)

            BinaryWriter流状态
            Poco::BinaryWriter提供了一些函数用于判断底层输出流的状态
            1. void flush()
            刷新底层流
            2. bool good()
            如果流状态正常返回真
            3. bool fail()
            如果底层流状态为fail返回真
            4. bool bad()
            如果底层流状态为bad返回真

10.2 Poco::BinaryReader

           Poco::BinaryReader为所有c++内置数据和std::string提供析取操作符。

            成员函数:
            1. voidread7BitEncoded(UInt32& value)
                voidread7BitEncoded(UInt64& value)
               读取以7位压缩格式存储的整形值。
            2. void readRaw(int length,std::string& value)
               读取指定长度的裸数据
            3. void readBOM()
               读取字节序标志BOM(byteorder mark),以便在内部自动激活字节序转换

            Poco::BinaryReader流状态
            Poco::BinaryReader提供了一些函数用于判断底层输出流的状态
            1. bool good()
            如果流状态正常返回真
            2. bool fail()
            如果底层流状态为fail返回真
            3. bool bad()
            如果底层流状态为bad返回真
            4. bool eof()
            如果底层状态为eof返回真

           下面是两个例子:

#include

using Poco::BinaryWriter;

int main(int argc, char** argv)

{

     std::ofstream ostr("binary.dat", std::ios::binary);

     BinaryWriter writer(ostr);

     writer.writeBOM();

     writer << "Hello, world!" << 42;

     writer.write7BitEncoded((Poco::UInt32)123);

     writer << true;

     return 0;

}

#include "Poco/BinaryReader.h"

#include

using Poco::BinaryReader;

int main(int argc, char** argv)

{

     std::ifstream istr("binary.dat", std::ios::binary);

     BinaryReader reader(istr);

     reader.readBOM();

     std::string hello;

     Poco::UInt32 i;

     bool b;

     reader >> hello >> i;

     reader.read7BitEncoded(i);

     reader >> b;

     return 0;

}

11. FileStream,FileInputStream, FileOutputStream

         FileStream、FileInputStream、FileOutputStream流用于读取和写入文件。在windows平台上,传给文件流的路径必须是UTF-8编码。Poco中文件流类总是以二进制方式打开文件,并且支持文件的seek操作。下面是一个例子:

#include

#include "Poco/FileStream.h"

#include "Poco/File.h"

#include "Poco/TemporaryFile.h"

#include "Poco/Exception.h"

int main(int argc, char** argv)

{

            Poco::FileOutputStream ostr("test.txt");

            ostr << "0123456789";

            ostr.close();

            Poco::FileStream str1("test.txt", std::ios::ate);

            int c = str1.get();

            assert (str1.eof());

            str1.clear();

            str1.seekg(0);

            c = str1.get();

            assert (c == '0');

            str1.close();

            Poco::FileStream str2("test.txt", std::ios::ate);

            str2 << "abcdef";

            str2.seekg(0);

            std::string s;

            str2 >> s;

            assert (s == "0123456789abcdef");

            str2.close();

            return 0;

}

参考文章:

     1. Zlib压缩算法系列1:LZ77算法

     2. 词典编码

     3. LZ77算法

     4. 笨笨数据压缩教程

     5. GZIP源代码分析

     6. 霍夫曼编码

     7. 循环冗余校验(CRC)算法入门引导

POCO C++库学习和分析 -- URI (Uniform Resource Identifiers)

1. 概述

          URI(RFC 3986)意为统一资源标记,通常被用来标志web上的资源。
          在Poco库中提供了POCO::URI、POCO::URIStreamFactory、POCO::URIStreamOpener类来对URI信息进行管理。其中POCO::URI用于进行URI操作和存储。URIStreamFactory可以打开一个URI资源,并且把该URI资源和一个输入流相关联。URIStreamOpener类用来设计成对URIStreamFactory进行管理。通过URIStreamFactory和URIStreamOpener可以把对所有资源的读取都适配一个流接口。

          下面是Poco中Uri部分的类图。

2. POCO::URI

          一个URI标志通常包括下列部分:
          Scheme:协议
          Authority:包括了主机地址、端口、用户信息(通常指用户名/密码)
          Path: 路径
          Query: 查询
          Fragment: 内部资源地址

          下面是URI的一些例子:

http    ://   www.google.com    /    search   ?    q=POCO

 |                  |                   |               |

 |                  |                   |               |

Scheme             Host                Path           Query

http    ://   appinf.com    /    poco/docs/Poco.URI.html    #   5589

 |                  |                       |                      |

 |                  |                       |                      |

Scheme             Host                    Path                 Fragment

ftp    ://   anonymous    @    upload.sourceforge.com    /   incoming

 |                  |                       |                      |

 |                  |                       |                      |

Scheme             User                    Host                   Path

POCO::URI类可以被看成是URI标记的集合。其接口定义如下:

class Foundation_API URI

{

public:

         URI();

                   ///Creates an empty URI.

         explicitURI(const std::string& uri);

                   ///Parses an URI from the given string. Throws a

                   ///SyntaxException if the uri is not valid.

         explicitURI(const char* uri);

                   ///Parses an URI from the given string. Throws a

                   ///SyntaxException if the uri is not valid.

         URI(conststd::string& scheme, const std::string& pathEtc);

                   ///Creates an URI from its parts.

         URI(conststd::string& scheme, const std::string& authority, conststd::string& pathEtc);

                   ///Creates an URI from its parts.

         URI(conststd::string& scheme, const std::string& authority, conststd::string& path, const std::string& query);

                   ///Creates an URI from its parts.

         URI(conststd::string& scheme, const std::string& authority, conststd::string& path, const std::string& query, const std::string&fragment);

                   ///Creates an URI from its parts.

         URI(constURI& uri);

                   ///Copy constructor. Creates an URI from another one.

         URI(constURI& baseURI, const std::string& relativeURI);

                   ///Creates an URI from a base URI and a relative URI, according to

                   ///the algorithm in section 5.2 of RFC 3986.

         ~URI();

                   ///Destroys the URI.

         URI&operator = (const URI& uri);

                   ///Assignment operator.

         URI&operator = (const std::string& uri);

                   ///Parses and assigns an URI from the given string. Throws a

                   ///SyntaxException if the uri is not valid.

         URI&operator = (const char* uri);

                   ///Parses and assigns an URI from the given string. Throws a

                   ///SyntaxException if the uri is not valid.

         voidswap(URI& uri);

                   ///Swaps the URI with another one.     

         voidclear();

                   ///Clears all parts of the URI.

         std::stringtoString() const;

                   ///Returns a string representation of the URI.

                   ///

                   ///Characters in the path, query and fragment parts will be

                   ///percent-encoded as necessary.

         conststd::string& getScheme() const;

                   ///Returns the scheme part of the URI.

         voidsetScheme(const std::string& scheme);

                   ///Sets the scheme part of the URI. The given scheme

                   ///is converted to lower-case.

                   ///

                   ///A list of registered URI schemes can be found

                   ///at http://www.iana.org/assignments/uri-schemes.

         conststd::string& getUserInfo() const;

                   ///Returns the user-info part of the URI.

         voidsetUserInfo(const std::string& userInfo);

                   ///Sets the user-info part of the URI.

         conststd::string& getHost() const;

                   ///Returns the host part of the URI.

         voidsetHost(const std::string& host);

                   ///Sets the host part of the URI.

         unsignedshort getPort() const;

                   ///Returns the port number part of the URI.

                   ///

                   ///If no port number (0) has been specified, the

                   ///well-known port number (e.g., 80 for http) for

                   ///the given scheme is returned if it is known.

                   ///Otherwise, 0 is returned.

         voidsetPort(unsigned short port);

                   ///Sets the port number part of the URI.

         std::stringgetAuthority() const;

                   ///Returns the authority part (userInfo, host and port)

                   ///of the URI.

                   ///

                   ///If the port number is a well-known port

                   ///number for the given scheme (e.g., 80 for http), it

                   ///is not included in the authority.

         voidsetAuthority(const std::string& authority);

                   ///Parses the given authority part for the URI and sets

                   ///the user-info, host, port components accordingly.

         conststd::string& getPath() const;

                   ///Returns the path part of the URI.

         voidsetPath(const std::string& path);

                   ///Sets the path part of the URI.

         std::stringgetQuery() const;

                   ///Returns the query part of the URI.

         voidsetQuery(const std::string& query);         

                   ///Sets the query part of the URI.

         conststd::string& getRawQuery() const;

                   ///Returns the unencoded query part of the URI.

         voidsetRawQuery(const std::string& query);  

                   ///Sets the query part of the URI.

         conststd::string& getFragment() const;

                   ///Returns the fragment part of the URI.

         voidsetFragment(const std::string& fragment);

                   ///Sets the fragment part of the URI.

         voidsetPathEtc(const std::string& pathEtc);

                   ///Sets the path, query and fragment parts of the URI.

         std::stringgetPathEtc() const;

                   ///Returns the path, query and fragment parts of the URI.

         std::stringgetPathAndQuery() const;

                   ///Returns the path and query parts of the URI.       

         voidresolve(const std::string& relativeURI);

                   ///Resolves the given relative URI against the base URI.

                   ///See section 5.2 of RFC 3986 for the algorithm used.

         voidresolve(const URI& relativeURI);

                   ///Resolves the given relative URI against the base URI.

                   ///See section 5.2 of RFC 3986 for the algorithm used.

         boolisRelative() const;

                   ///Returns true if the URI is a relative reference, false otherwise.

                   ///

                   ///A relative reference does not contain a scheme identifier.

                   ///Relative references are usually resolved against an absolute

                   ///base reference.

         boolempty() const;

                   ///Returns true if the URI is empty, false otherwise.

         booloperator == (const URI& uri) const;

                   ///Returns true if both URIs are identical, false otherwise.

                   ///

                   ///Two URIs are identical if their scheme, authority,

                   ///path, query and fragment part are identical.

         booloperator == (const std::string& uri) const;

                   ///Parses the given URI and returns true if both URIs are identical,

                   ///false otherwise.

         booloperator != (const URI& uri) const;

                   ///Returns true if both URIs are identical, false otherwise.

         booloperator != (const std::string& uri) const;

                   ///Parses the given URI and returns true if both URIs are identical,

                   ///false otherwise.

         voidnormalize();

                   ///Normalizes the URI by removing all but leading . and .. segments from the path.

                   ///

                   ///If the first path segment in a relative path contains a colon (:),

                   ///such as in a Windows path containing a drive letter, a dot segment (./)

                   ///is prepended in accordance with section 3.3 of RFC 3986.

         voidgetPathSegments(std::vector& segments);

                   ///Places the single path segments (delimited by slashes) into the

                   ///given vector.

         staticvoid encode(const std::string& str, const std::string& reserved,std::string& encodedStr);

                   ///URI-encodes the given string by escaping reserved and non-ASCII

                   ///characters. The encoded string is appended to encodedStr.

         staticvoid decode(const std::string& str, std::string& decodedStr);

                   ///URI-decodes the given string by replacing percent-encoded

                   ///characters with the actual character. The decoded string

                   ///is appended to decodedStr.

         ….

private:

         std::string    _scheme;

         std::string    _userInfo;

         std::string    _host;

         unsignedshort _port;

         std::string    _path;

         std::string    _query;

         std::string    _fragment;

};

 可以看到,在POCO::URI内部单独定义了不同字段来对应存储URI的不同部分。值得注意到是,在POCO::URI中存在两个函静态数encode()和decode()。这两个函数用来对URI资源进行Percent-encoding编码。这是由于在URI标准中定义了一些保留字符,如果URI资源中存在保留字符,必须对它们进行转义。

          下面是其一个例子:

#include "Poco/URI.h"

#include

int main(int argc, char** argv)

{

         Poco::URIuri1("http://www.appinf.com:88/sample?example-query#frag");

         std::stringscheme(uri1.getScheme()); // "http"

         std::stringauth(uri1.getAuthority()); // "www.appinf.com:88"

         std::stringhost(uri1.getHost()); // "www.appinf.com"

         unsignedshort port = uri1.getPort(); // 88

         std::stringpath(uri1.getPath()); // "/sample"

         std::stringquery(uri1.getQuery()); // "example-query"

         std::stringfrag(uri1.getFragment()); // "frag"

         std::stringpathEtc(uri1.getPathEtc()); // "/sample?examplequery#frag"

         Poco::URIuri2;

         uri2.setScheme("https");

         uri2.setAuthority("www.appinf.com");

         uri2.setPath("/anothersample");

         std::strings(uri2.toString());

         // "https://www.appinf.com/another%20sample"

         Poco::URIuri3("http://www.appinf.com");

         uri3.resolve("/poco/info/index.html");

         s =uri3.toString(); // "http://www.appinf.com/poco/info/index.html"

         uri3.resolve("support.html");

         s =uri3.toString(); // "http://www.appinf.com/poco/info/support.html"

         uri3.resolve("http://sourceforge.net/projects/poco");

         s =uri3.toString(); // "http://sourceforge.net/projects/poco"

         return0;

}

3. POCO::URIStreamOpener

         POCO::URIStreamOpener类主要用于为URI资源,创建并打开一个对应的输入流。对于每一种URI协议,Poco::URIStreamFactory的子类必须向POCO::URIStreamOpener注册。
          在Poco库中,内置了文件(包括网络文件) 流工厂类FileStreamFactory,HTTP流工厂类HTTPStreamFactory,FTP流工厂类 FTPStreamFactory。

          下面是其一个例子:

#include "Poco/URIStreamOpener.h"

#include "Poco/Net/HTTPStreamFactory.h"

#include "Poco/Net/FTPStreamFactory.h"

#include

int main(int argc, char** argv)

{

         Poco::Net::HTTPStreamFactory::registerFactory();

         Poco::Net::FTPStreamFactory::registerFactory();

         Poco::URIStreamOpener&opener =

                   Poco::URIStreamOpener::defaultOpener();

         std::auto_ptristr1(

                   opener.open("http://www.appinf.com/index.html")

                   );

         std::auto_ptristr2(

                   opener.open("ftp://ftp.appinf.com/pub/poco/poco-1.2.5.tar.gz")

                   );

         std::auto_ptristr3(

                   opener.open("file:///usr/include/stdio.h")

                   );

         return0;

}

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

1. 概述

             通用唯一识别码(英语:UniversallyUnique Identifier,简称UUID)是一种软件建构的标准。UUID的目的,是让分散式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。一组UUID,是由一串16位组(亦称128位)的16进位数字所构成,是故UUID理论上的总数为216 x 8=2128,约等于3.4 x 1038。UUID的标准型式包含32个16进位数字,以连字号分为五段,形式为8-4-4-4-12的36个字符。

            Poco中提供了Poco::UUID类来存储UUID信息,提供了Poco::UUIDGenerator类生成UUID信息。其类图如下:

2. Poco::UUID与Poco::UUIDGenerator类

            Poco::UUID类很简单,用于存储UUID信息,支持所有的值语义,包括所有的关系运算符,也支持字符串之间的相互准换。
            Poco::UUIDGenerator类用于生成UUID信息。UUIDGenerator类生成UUID信息的方法有3中,分别是基于时间生成、基于名字(数字摘要)生成、基于随机数生成。

             基于时间的函数如下:

UUID UUIDGenerator::create()

{

              FastMutex::ScopedLock lock(_mutex);

              if (!_haveNode)

              {

                            Environment::nodeId(_node);

                            _haveNode = true;

              }

              Timestamp::UtcTimeVal tv = timeStamp();

              UInt32 timeLow = UInt32(tv & 0xFFFFFFFF);

              UInt16 timeMid = UInt16((tv >> 32) & 0xFFFF);

              UInt16 timeHiAndVersion = UInt16((tv >> 48) &0x0FFF) + (UUID::UUID_TIME_BASED << 12);

              UInt16 clockSeq = (UInt16(_random.next() >> 4)& 0x3FFF) | 0x8000;

              return UUID(timeLow, timeMid, timeHiAndVersion,clockSeq, _node);

}

        从上面我们看到,其获取了Mac地址信息和当前时间戳,最终生成uuid信息。Poco中获取Mac地址,可见文章<平台与环境>
             基于名称的生成函数如下:

UUID UUIDGenerator::createFromName(constUUID& nsid, const std::string& name, DigestEngine& de)

{

         poco_assert_dbg(de.digestLength() >= 16);

         UUIDnetNsid = nsid;

         netNsid.toNetwork();

         de.reset();

         de.update(&netNsid._timeLow,sizeof(netNsid._timeLow));

         de.update(&netNsid._timeMid,sizeof(netNsid._timeMid));

         de.update(&netNsid._timeHiAndVersion,sizeof(netNsid._timeHiAndVersion));

         de.update(&netNsid._clockSeq,sizeof(netNsid._clockSeq));

         de.update(&netNsid._node[0],sizeof(netNsid._node));

         de.update(name);

         charbuffer[16];

         constDigestEngine::Digest& d = de.digest();

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

         {

                   buffer[i]= d[i];

         }

         returnUUID(buffer, UUID::UUID_NAME_BASED);

}

  从上面我们看到,其根据一个已有的uuid和一个字符串生成了16位的数字摘要。获取数字摘要,可见文章<随机数和数字摘要>
             基于随机数的生成函数如下:

UUIDUUIDGenerator::createRandom()

{

              char buffer[16];

              RandomInputStream ris;

              ris.read(buffer, sizeof(buffer));

              return UUID(buffer, UUID::UUID_RANDOM);

}

从上面我们看到,其生成了一串16个字符的数组。获取随机数,可见文章<随机数和数字摘要>
             下面是其使用的一个例子:

#include "Poco/UUID.h"

#include"Poco/UUIDGenerator.h"

#include

using Poco::UUID;

using Poco::UUIDGenerator;

int main(int argc, char**argv)

{

              UUIDGenerator& generator =UUIDGenerator::defaultGenerator();

              UUID uuid1(generator.create()); // time based

              UUID uuid2(generator.createRandom());

              UUID uuid3(generator.createFromName(UUID::uri(),"http://appinf.com"));

              std::cout << uuid1.toString() << std::endl;

              std::cout << uuid2.toString() << std::endl;

              std::cout << uuid3.toString() << std::endl;

              return 0;

}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章