程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> POCO C++庫學習和分析 -- 異常、錯誤處理、調試

POCO C++庫學習和分析 -- 異常、錯誤處理、調試

編輯: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: 用來定義異常宏的執行體


        兩個宏分別定義如下:

[cpp]
// MyException.h  
#include "Poco/Exception.h"  
POCO_DECLARE_EXCEPTION(MyLib_API, MyException, Poco::Exception) 

// MyException.h
#include "Poco/Exception.h"
POCO_DECLARE_EXCEPTION(MyLib_API, MyException, Poco::Exception)
[cpp]
// MyException.cpp  
#include "MyException.h"POCO_IMPLEMENT_EXCEPTION(MyException, Poco::Exception,"Something really bad happened...") 

// MyException.cpp
#include "MyException.h"POCO_IMPLEMENT_EXCEPTION(MyException, Poco::Exception,"Something really bad happened...")

 

        宏展開分別為:
[cpp]
// 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.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;
 ...
};
[cpp]
// 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..."; 

... 

// 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...";
}
...

        下面是一個例子:
[cpp]
#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; 

#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。比如:
[cpp]
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"); 
    } 

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下,調用了
[cpp]
BOOL WINAPI IsDebuggerPresent(VOID); 
VOID WINAPI DebugBreak(VOID); 

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對象則完成了上下文的入棧工作。下面是一個例子:

[cpp]
#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; 

#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對象。宏定義如下:
[cpp]
#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 

#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實現了此功能。其定義如下:


[cpp]
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(); 

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()是個靜態函數,其定義如下:


[cpp]
namespace 

    static ThreadLocal<NestedDiagnosticContext> ndc; 

 
NestedDiagnosticContext& NestedDiagnosticContext::current() 

    return ndc.get(); 

namespace
{
 static ThreadLocal<NestedDiagnosticContext> ndc;
}

NestedDiagnosticContext& NestedDiagnosticContext::current()
{
 return ndc.get();
}


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


[cpp]
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&); 
}; 

template <class C>
class ThreadLocal
 /// This template is used to declare type safe thread
 /// local variables. It can basically be used like
 /// a smart pointer class with the special feature
 /// that it references a different object
 /// in every thread. The underlying object will
 /// be created when it is referenced for the first
 /// time.
 /// See the NestedDiagnosticContext class for an
 /// example how to use this template.
 /// Every thread only has access to its own
 /// thread local data. There is no way for a thread
 /// to access another thread's local data.
{
 typedef TLSSlot<C> Slot;

public:
 ThreadLocal()
 {
 }
 
 ~ThreadLocal()
 {
 }
 
 C* operator -> ()
 {
  return &get();
 }
 
 C& operator * ()
  /// "Dereferences" the smart pointer and returns a reference
  /// to the underlying data object. The reference can be used
  /// to modify the object.
 {
  return get();
 }

 C& get()
  /// Returns a reference to the underlying data object.
  /// The reference can be used to modify the object.
 {
  TLSAbstractSlot*& p = ThreadLocalStorage::current().get(this);
  if (!p) p = new Slot;
  return static_cast<Slot*>(p)->value();
 }
 
private:
 ThreadLocal(const ThreadLocal&);
 ThreadLocal& operator = (const ThreadLocal&);
};


        到這裡Poco中所有的NDC流程都被打通了,用戶終於可以實現按線程打印日志信息了。


 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved