POCO C++库学习和分析 -- 异常、错误处理、调试
POCO C++库学习和分析 -- 异常、错误处理、调试
1. 异常处理
C++同C语言相比,提供了异常机制。通过使用try,catch关键字可以捕获异常,这种机制使得程序员在程序异常发生时,可以通过判断异常类型,来决定程序是否继续执行,并在程序结束之前优雅的释放各类资源。当然对于C++的异常机制也存在着很多的争议。在这里,并不对此展开讨论,只介绍一下Poco中的异常类。Poco中的异常类:
1. 所有的异常类都是Poco::Exception的子类。
2. Poco::Exception继承自std::exception类。
3. Foundation库中涉及的异常类,包括了下面一些:
a) Poco::LogicException类负责处理程序错误,包括了:
AssertionViolationException
NullPointerException
NullValueException
BugcheckException
InvalidArgumentException
NotImplementedException
RangeException
IllegalStateException
InvalidAccessException
SignalException
UnhandledException
b) Poco::ApplicationException类负责处理应用程序相关的错误,即使用Poco库的用户自定义异常。
c) Poco::RuntimeException类负责处理程序运行时的错误,包括了:
RuntimeException
NotFoundException
ExistsException
TimeoutException
SystemException
RegularExpressionException
LibraryLoadException
LibraryAlreadyLoadedException
NoThreadAvailableException
PropertyNotSupportedException
PoolOverflowException
NoPermissionException
OutOfMemoryException
DataException
DataFormatException
SyntaxException
CircularReferenceException
PathSyntaxException
IOException
ProtocolException
FileException
FileExistsException
FileNotFoundException
PathNotFoundException
FileReadOnlyException
FileAccessDeniedException
CreateFileException
OpenFileException
WriteFileException
ReadFileException
UnknownURISchemeException
成员函数及数据定义:
1. Poco::Exception包括了一个名字,这是一个静态的字符串,用来描述异常本身。比如说LogicException名字为"Logic exception",TimeoutException名字为"Timeout"。
2. Poco::Exception还包含了一个字符串消息,这是用来进一步描述异常的。使用的的人可以在运行时定义它。比如都是LogicException异常,函数一处抛出异常时可定义为"Function1",函数二处抛出时异常时可定义为用"Function2",它可以用来说明异常发生的具体位置和原因。
3. 一个可选的嵌套异常类
4. 构造函数:
a) 可以使用0个,1个或2个字符串参数来构造异常。在Poco::Exception内部存储的时候,第二个字符串会使用字符":"和第一个字符串串联。
b) 构造时如果使用了字符串和嵌套异常的方式,嵌套异常会被复制一份。
5. Poco::Exception支持拷贝和赋值运算符
6. const char* name()
返回异常的名称
7. const std::string& message()
返回在构造时传入的消息字符串
8. std::string displayText() const
同时返回异常名字和消息字符串,中间使用": "分隔
9. const Exception* nested() const
如果存在嵌套异常的话,返回之歌指向嵌套异常的指针,否则返回0
10. Exception* clone() const
返回一个异常的拷贝
11. void rethrow() const
重新抛出异常
定义自己的异常:
因为从Poco::Exception继承,去定义自己的异常时,工作非常的枯燥且重复(用户需要重载大量的虚函数),在库中提供了两个宏来完成这个工作:
POCO_DECLARE_EXCEPTION:用来申明异常宏
POCO_IMPLEMENT_EXCEPTION: 用来定义异常宏的执行体
两个宏分别定义如下:
// MyException.h #include "Poco/Exception.h" POCO_DECLARE_EXCEPTION(MyLib_API, MyException, Poco::Exception)
// MyException.cpp #include "MyException.h"POCO_IMPLEMENT_EXCEPTION(MyException, Poco::Exception,"Something really bad happened...")
// MyException.h #include "Poco/Exception.h" POCO_DECLARE_EXCEPTION(MyLib_API, MyException, Poco::Exception) class MyLib_API MyException: public Poco::Exception { public: MyException(); MyException(const std::string& msg); MyException(const std::string& msg, const std::string& arg); MyException(const std::string& msg, const Poco::Exception& nested); MyException(const MyException& exc); ~MyException(); MyException& operator = (const MyException& exc); const char* name() const; ... };
// MyException.cpp #include "MyException.h" POCO_IMPLEMENT_EXCEPTION(MyException, Poco::Exception, "Something really bad happened...") ... const char* MyException::name() const throw() { return "Something really bad happened..."; } ...
下面是一个例子:
#include "Poco/Exception.h" #include <iostream> int main(int argc, char** argv) { Poco::Exception* pExc = 0; try { throw Poco::ApplicationException("just testing"); } catch (Poco::Exception& exc) { pExc = exc.clone(); } try { pExc->rethrow(); } catch (Poco::Exception& exc) { std::cerr << exc.displayText() << std::endl; } delete pExc; return 0; }
2. 断言
POCO库中提供了一些断言的宏来进行运行时检查,这些断言能够提供出错代码的行号和文件信息。1. Debugger::_assert(cond)
如果cond ≠ true时,抛出一个AssertionViolationException异常。
2. poco_assert_dbg(cond)
同poco_assert类似,但是只在debug模式下起作用
3. poco_check_ptr(ptr)
如果ptr为空,则抛出NullPointerException异常
4. poco_bugcheck(), poco_bugcheck_msg(string)
抛出BugcheckException异常
POCO的断言类在debug调试模式下(比如在Visual C++)中时,会触发一个breakpoint。比如:
void foo(Bar* pBar) { poco_check_ptr (pBar); ... } void baz(int i) { poco_assert (i >= 1 && i < 3); switch (i) { case 1: ... break; case 2: ... break; default: poco_bugcheck_msg("i has invalid value"); } }
这主要是因为Poco中的断言类是通过Poco::Debugger去实现的,在Poco::Debugger底层调用了不同操作系统的API,去判断程序是否处于调试状态。如VC下,调用了
BOOL WINAPI IsDebuggerPresent(VOID); VOID WINAPI DebugBreak(VOID);
3. NDC(Nested Diagnostic Context)
3.1 概述
NestedDiagnosticContext是为了多线程诊断而设计的。我们在写程序时,一般都需要同时处理多个线程。为了更加便捷的处理多线程情况,为每个线程产生各自的日志。Neil Harrison 在他的书中"Patterns for Logging Diagnostic Messages," in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997) 中提出了一个方法。独特地标记每个日志请求,用户把上下文信息送入NDC,NDC是 Nested Diagnostic Context的缩写。在这本书里提到了3种日志方法,分别是:1. DiagnosticLogger
分离日志和程序其他模块
2. TransactionalBuckets
事务桶,为事务单独建立日志
3. TypedDiagnostics
类型化诊断,为所有的诊断信息提供统一的展现
我们还是回到Poco中的NDC上。在Poco中和NDC相关的内容包括了,NestedDiagnosticContext类,NDCScope类,宏poco_ndc和poco_ndc_dbg。其中NestedDiagnosticContext类维护一个NDC对象,其中包括了上下文的栈信息,有函数方法名,源文件代码文件名,行号。宏poco_ndc(func) or poco_ndc_dbg(func)申明了一个NDCScope对象。而NDCScope对象则完成了上下文的入栈工作。下面是一个例子:
#include "Poco/NestedDiagnosticContext.h" #include <iostream> void f1() { poco_ndc(f1); Poco::NDC::current().dump(std::cout); } void f2() { poco_ndc(f2); f1(); } int main(int argc, char** argv) { f2(); return 0; }
3.2 实现
3.2.1 线程本地存储
在Poco中实现时,用了一些小技巧,即线程本地存储。我们来看Poco中TLS的类图:CurrentThreadHolder类是TLS实现的具体类,在每个Thread对象中包含了一个CurrentThreadHolder对象。Thread创建的时候,CurrentThreadHolder会调用不同操作系统的API函数,获取并保存一个固定槽位,用于保存Thread对象的指针。
每个Thread对象中还包含了一个ThreadLocalStorage对象。ThreadLocalStorage类用于保存具体的线程信息数据,它是一个TLSSlot对象的集合。通过泛型实现TLSSlot后,ThreadLocalStorage可用于保存任何数据的。
使用了TLS技术后,调用Thread的静态函数current可以获取到每个线程对象Thread的指针,然后再通过这个Thread对象的指针,可以获取到ThreadLocalStorage对象,并最终获取或保存数据于TLSSlot中。
通过类的静态函数获取类实例的指针,在C++中是不存在的,这需要操作系统支持,只有Thread对象才能做到这一点。
3.2.2 NDC
在来看一张Poco中NDC类的类图:使用者通过调用宏poco_ndc和poco_ndc_dbg,来构建一个NDCScope对象。宏定义如下:
#define poco_ndc(func) \ Poco::NDCScope _theNdcScope(#func, __LINE__, __FILE__) #if defined(_DEBUG) #define poco_ndc_dbg(func) \ Poco::NDCScope _theNdcScope(#func, __LINE__, __FILE__) #else #define poco_ndc_dbg(func) #endif
NDCScope实现了诊断信息上下文的入栈出栈工作,它通过调用NestedDiagnosticContext类的静态函数current实现了此功能。其定义如下:
inline NDCScope::NDCScope(const std::string& info) { NestedDiagnosticContext::current().push(info); } inline NDCScope::NDCScope(const std::string& info, int line, const char* filename) { NestedDiagnosticContext::current().push(info, line, filename); } inline NDCScope::~NDCScope() { NestedDiagnosticContext::current().pop(); }
NestedDiagnosticContext类的current()是个静态函数,其定义如下:
namespace { static ThreadLocal<NestedDiagnosticContext> ndc; } NestedDiagnosticContext& NestedDiagnosticContext::current() { return ndc.get(); }
而ThreadLocal是一个辅助类,用于获取线程对象的本地存储信息或者是主线程的本地存储信息。
template <class C> class ThreadLocal /// This template is used to declare type safe thread /// local variables. It can basically be used like /// a smart pointer class with the special feature /// that it references a different object /// in every thread. The underlying object will /// be created when it is referenced for the first /// time. /// See the NestedDiagnosticContext class for an /// example how to use this template. /// Every thread only has access to its own /// thread local data. There is no way for a thread /// to access another thread's local data. { typedef TLSSlot<C> Slot; public: ThreadLocal() { } ~ThreadLocal() { } C* operator -> () { return &get(); } C& operator * () /// "Dereferences" the smart pointer and returns a reference /// to the underlying data object. The reference can be used /// to modify the object. { return get(); } C& get() /// Returns a reference to the underlying data object. /// The reference can be used to modify the object. { TLSAbstractSlot*& p = ThreadLocalStorage::current().get(this); if (!p) p = new Slot; return static_cast<Slot*>(p)->value(); } private: ThreadLocal(const ThreadLocal&); ThreadLocal& operator = (const ThreadLocal&); };
到这里Poco中所有的NDC流程都被打通了,用户终于可以实现按线程打印日志信息了。
(版权所有,转载时请注明作者和出处 http://blog.csdn.net/arau_sh/article/details/8698353)