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

本是古典 何须时尚 2022-03-20 05:35 669阅读 0赞

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

1. 异常处理

  1. C++同C语言相比,提供了异常机制。通过使用try,catch关键字可以捕获异常,这种机制使得程序员在程序异常发生时,可以通过判断异常类型,来决定程序是否继续执行,并在程序结束之前优雅的释放各类资源。当然对于C++的异常机制也存在着很多的争议。在这里,并不对此展开讨论,只介绍一下Poco中的异常类。
  2. **Poco中的异常类:**
  3. 1. 所有的异常类都是Poco::Exception的子类。
  4. 2. Poco::Exception继承自std::exception类。
  5. 3. Foundation库中涉及的异常类,包括了下面一些:
  6. a) Poco::LogicException类负责处理程序错误,包括了:
  7. AssertionViolationException
  8. NullPointerException
  9. NullValueException
  10. BugcheckException
  11. InvalidArgumentException
  12. NotImplementedException
  13. RangeException
  14. IllegalStateException
  15. InvalidAccessException
  16. SignalException
  17. UnhandledException
  18. b) Poco::ApplicationException类负责处理应用程序相关的错误,即使用Poco库的用户自定义异常。
  19. c) Poco::RuntimeException类负责处理程序运行时的错误,包括了:
  20. RuntimeException
  21. NotFoundException
  22. ExistsException
  23. TimeoutException
  24. SystemException
  25. RegularExpressionException
  26. LibraryLoadException
  27. LibraryAlreadyLoadedException
  28. NoThreadAvailableException
  29. PropertyNotSupportedException
  30. PoolOverflowException
  31. NoPermissionException
  32. OutOfMemoryException
  33. DataException
  34. DataFormatException
  35. SyntaxException
  36. CircularReferenceException
  37. PathSyntaxException
  38. IOException
  39. ProtocolException
  40. FileException
  41. FileExistsException
  42. FileNotFoundException
  43. PathNotFoundException
  44. FileReadOnlyException
  45. FileAccessDeniedException
  46. CreateFileException
  47. OpenFileException
  48. WriteFileException
  49. ReadFileException
  50. UnknownURISchemeException

成员函数及数据定义:

  1. 1. Poco::Exception包括了一个名字,这是一个静态的字符串,用来描述异常本身。比如说LogicException名字为"Logic exception",TimeoutException名字为"Timeout"
  2. 2. Poco::Exception还包含了一个字符串消息,这是用来进一步描述异常的。使用的的人可以在运行时定义它。比如都是LogicException异常,函数一处抛出异常时可定义为"Function1",函数二处抛出时异常时可定义为用"Function2",它可以用来说明异常发生的具体位置和原因。
  3. 3. 一个可选的嵌套异常类
  4. 4. 构造函数:
  5. a) 可以使用0个,1个或2个字符串参数来构造异常。在Poco::Exception内部存储的时候,第二个字符串会使用字符":"和第一个字符串串联。
  6. b) 构造时如果使用了字符串和嵌套异常的方式,嵌套异常会被复制一份。
  7. 5. Poco::Exception支持拷贝和赋值运算符
  8. 6. const char\* name()
  9. 返回异常的名称
  10. 7. const std::string& message()
  11. 返回在构造时传入的消息字符串
  12. 8. std::string displayText() const
  13. 同时返回异常名字和消息字符串,中间使用": "分隔
  14. 9. const Exception\* nested() const
  15. 如果存在嵌套异常的话,返回之歌指向嵌套异常的指针,否则返回0
  16. 10. Exception\* clone() const
  17. 返回一个异常的拷贝
  18. 11. void rethrow() const
  19. 重新抛出异常

定义自己的异常:
因为从Poco::Exception继承,去定义自己的异常时,工作非常的枯燥且重复(用户需要重载大量的虚函数),在库中提供了两个宏来完成这个工作:
POCO_DECLARE_EXCEPTION:用来申明异常宏
POCO_IMPLEMENT_EXCEPTION: 用来定义异常宏的执行体

  1. 两个宏分别定义如下:
  2. // MyException.h
  3. #include "Poco/Exception.h"
  4. POCO_DECLARE_EXCEPTION(MyLib_API, MyException, Poco::Exception)
  5. // MyException.cpp
  6. #include "MyException.h"POCO_IMPLEMENT_EXCEPTION(MyException, Poco::Exception,"Something really bad happened...")
  7. 宏展开分别为:
  8. // MyException.h
  9. #include "Poco/Exception.h"
  10. POCO_DECLARE_EXCEPTION(MyLib_API, MyException, Poco::Exception)
  11. class MyLib_API MyException: public Poco::Exception
  12. {
  13. public:
  14. MyException();
  15. MyException(const std::string& msg);
  16. MyException(const std::string& msg, const std::string& arg);
  17. MyException(const std::string& msg, const Poco::Exception& nested);
  18. MyException(const MyException& exc);
  19. ~MyException();
  20. MyException& operator = (const MyException& exc);
  21. const char* name() const;
  22. ...
  23. };
  24. // MyException.cpp
  25. #include "MyException.h"
  26. POCO_IMPLEMENT_EXCEPTION(MyException, Poco::Exception,
  27. "Something really bad happened...")
  28. ...
  29. const char* MyException::name() const throw()
  30. {
  31. return "Something really bad happened...";
  32. }
  33. ...
  34. 下面是一个例子:
  35. #include "Poco/Exception.h"
  36. #include <iostream>
  37. int main(int argc, char** argv)
  38. {
  39. Poco::Exception* pExc = 0;
  40. try
  41. {
  42. throw Poco::ApplicationException("just testing");
  43. }
  44. catch (Poco::Exception& exc)
  45. {
  46. pExc = exc.clone();
  47. }
  48. try
  49. {
  50. pExc->rethrow();
  51. }
  52. catch (Poco::Exception& exc)
  53. {
  54. std::cerr << exc.displayText() << std::endl;
  55. }
  56. delete pExc;
  57. return 0;
  58. }

2. 断言

  1. POCO库中提供了一些断言的宏来进行运行时检查,这些断言能够提供出错代码的行号和文件信息。
  2. 1. Debugger::\_assert(cond)
  3. 如果cond true时,抛出一个AssertionViolationException异常。
  4. 2. poco\_assert\_dbg(cond)
  5. poco\_assert类似,但是只在debug模式下起作用
  6. 3. poco\_check\_ptr(ptr)
  7. 如果ptr为空,则抛出NullPointerException异常
  8. 4. poco\_bugcheck(), poco\_bugcheck\_msg(string)
  9. 抛出BugcheckException异常
  10. POCO的断言类在debug调试模式下(比如在Visual C++)中时,会触发一个breakpoint。比如:
  11. void foo(Bar* pBar)
  12. {
  13. poco_check_ptr (pBar);
  14. ...
  15. }
  16. void baz(int i)
  17. {
  18. poco_assert (i >= 1 && i < 3);
  19. switch (i)
  20. {
  21. case 1:
  22. ...
  23. break;
  24. case 2:
  25. ...
  26. break;
  27. default:
  28. poco_bugcheck_msg("i has invalid value");
  29. }
  30. }
  31. 这主要是因为Poco中的断言类是通过Poco::Debugger去实现的,在Poco::Debugger底层调用了不同操作系统的API,去判断程序是否处于调试状态。如VC下,调用了
  32. BOOL WINAPI IsDebuggerPresent(VOID);
  33. VOID WINAPI DebugBreak(VOID);

3. NDC(Nested Diagnostic Context)

3.1 概述

  1. NestedDiagnosticContext是为了多线程诊断而设计的。我们在写程序时,一般都需要同时处理多个线程。为了更加便捷的处理多线程情况,为每个线程产生各自的日志。Neil Harrison 在他的书中" [Patterns for Logging Diagnostic Messages,][Patterns for Logging Diagnostic Messages]" in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997) 中提出了一个方法。独特地标记每个日志请求,用户把上下文信息送入NDCNDC Nested Diagnostic Context的缩写。在这本书里提到了3种日志方法,分别是:
  2. 1. DiagnosticLogger
  3. 分离日志和程序其他模块
  4. 2. TransactionalBuckets
  5. 事务桶,为事务单独建立日志
  6. 3. TypedDiagnostics
  7. 类型化诊断,为所有的诊断信息提供统一的展现

我们还是回到Poco中的NDC上。在Poco中和NDC相关的内容包括了,NestedDiagnosticContext类,NDCScope类,宏poco_ndc和poco_ndc_dbg。其中NestedDiagnosticContext类维护一个NDC对象,其中包括了上下文的栈信息,有函数方法名,源文件代码文件名,行号。宏poco_ndc(func) or poco_ndc_dbg(func)申明了一个NDCScope对象。而NDCScope对象则完成了上下文的入栈工作。下面是一个例子:

  1. #include "Poco/NestedDiagnosticContext.h"
  2. #include <iostream>
  3. void f1()
  4. {
  5. poco_ndc(f1);
  6. Poco::NDC::current().dump(std::cout);
  7. }
  8. void f2()
  9. {
  10. poco_ndc(f2);
  11. f1();
  12. }
  13. int main(int argc, char** argv)
  14. {
  15. f2();
  16. return 0;
  17. }

3.2 实现

3.2.1 线程本地存储

  1. Poco中实现时,用了一些小技巧,即线程本地存储。我们来看PocoTLS的类图:

1365315376_5638.jpg

  1. CurrentThreadHolder类是TLS实现的具体类,在每个Thread对象中包含了一个CurrentThreadHolder对象。Thread创建的时候,CurrentThreadHolder会调用不同操作系统的API函数,获取并保存一个固定槽位,用于保存Thread对象的指针。
  2. 每个Thread对象中还包含了一个ThreadLocalStorage对象。ThreadLocalStorage类用于保存具体的线程信息数据,它是一个TLSSlot对象的集合。通过泛型实现TLSSlot后,ThreadLocalStorage可用于保存任何数据的。
  3. 使用了TLS技术后,调用Thread的静态函数current可以获取到每个线程对象Thread的指针,然后再通过这个Thread对象的指针,可以获取到ThreadLocalStorage对象,并最终获取或保存数据于TLSSlot中。
  4. 通过类的静态函数获取类实例的指针,在C++中是不存在的,这需要操作系统支持,只有Thread对象才能做到这一点。

3.2.2 NDC

  1. 在来看一张PocoNDC类的类图:

1365315389_7711.jpg

  1. 使用者通过调用宏poco\_ndcpoco\_ndc\_dbg,来构建一个NDCScope对象。宏定义如下:
  2. #define poco_ndc(func) \
  3. Poco::NDCScope _theNdcScope(#func, __LINE__, __FILE__)
  4. #if defined(_DEBUG)
  5. #define poco_ndc_dbg(func) \
  6. Poco::NDCScope _theNdcScope(#func, __LINE__, __FILE__)
  7. #else
  8. #define poco_ndc_dbg(func)
  9. #endif
  10. NDCScope实现了诊断信息上下文的入栈出栈工作,它通过调用NestedDiagnosticContext类的静态函数current实现了此功能。其定义如下:
  11. inline NDCScope::NDCScope(const std::string& info)
  12. {
  13. NestedDiagnosticContext::current().push(info);
  14. }
  15. inline NDCScope::NDCScope(const std::string& info, int line, const char* filename)
  16. {
  17. NestedDiagnosticContext::current().push(info, line, filename);
  18. }
  19. inline NDCScope::~NDCScope()
  20. {
  21. NestedDiagnosticContext::current().pop();
  22. }
  23. NestedDiagnosticContext类的current()是个静态函数,其定义如下:
  24. namespace
  25. {
  26. static ThreadLocal<NestedDiagnosticContext> ndc;
  27. }
  28. NestedDiagnosticContext& NestedDiagnosticContext::current()
  29. {
  30. return ndc.get();
  31. }
  32. ThreadLocal是一个辅助类,用于获取线程对象的本地存储信息或者是主线程的本地存储信息。
  33. template <class C>
  34. class ThreadLocal
  35. /// This template is used to declare type safe thread
  36. /// local variables. It can basically be used like
  37. /// a smart pointer class with the special feature
  38. /// that it references a different object
  39. /// in every thread. The underlying object will
  40. /// be created when it is referenced for the first
  41. /// time.
  42. /// See the NestedDiagnosticContext class for an
  43. /// example how to use this template.
  44. /// Every thread only has access to its own
  45. /// thread local data. There is no way for a thread
  46. /// to access another thread's local data.
  47. {
  48. typedef TLSSlot<C> Slot;
  49. public:
  50. ThreadLocal()
  51. {
  52. }
  53. ~ThreadLocal()
  54. {
  55. }
  56. C* operator -> ()
  57. {
  58. return &get();
  59. }
  60. C& operator * ()
  61. /// "Dereferences" the smart pointer and returns a reference
  62. /// to the underlying data object. The reference can be used
  63. /// to modify the object.
  64. {
  65. return get();
  66. }
  67. C& get()
  68. /// Returns a reference to the underlying data object.
  69. /// The reference can be used to modify the object.
  70. {
  71. TLSAbstractSlot*& p = ThreadLocalStorage::current().get(this);
  72. if (!p) p = new Slot;
  73. return static_cast<Slot*>(p)->value();
  74. }
  75. private:
  76. ThreadLocal(const ThreadLocal&);
  77. ThreadLocal& operator = (const ThreadLocal&);
  78. };
  79. 到这里Poco中所有的NDC流程都被打通了,用户终于可以实现按线程打印日志信息了。

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

发表评论

表情:
评论列表 (有 0 条评论,669人围观)

还没有评论,来说两句吧...

相关阅读