異常處理與MiniDump詳解【轉帖】
(1) C++異常
一、 綜述
我很少敢為自己寫的東西弄個詳解的標題,之所以這次敢於這樣,自然還算是有點底氣的。並且也以此為動力,督促自己好好的將這兩個東西研究透。
當年剛開始工作的時候,第一個工作就是學習breakpad的原始碼,然後瞭解其原理,為公司寫一個ExceptionHandle的庫,以處理伺服器及客戶端的未處理異常(unhandle exception),並打下dump,以便事後分析,當年這個功能在有breakpad的示例在前時,實現難度並不大,無非就是呼叫了SetUnhandledExceptionFilter等函式,讓windows在出現未處理異常時讓自己的回撥函式接管操作,然後利用其struct _EXCEPTION_POINTERS* ExceptionInfo的指標,通過MiniDumpWriteDump API將Dump寫下來。但是仍記得,那時看到《Windows 核心程式設計》第五部分關於結構化異常處理的描述時那種因為得到新鮮知識時的興奮感,那是我第一次這樣接近Windows系統的底層機制,如同以前很多次說過的,那以後我很投入的捧著讀完了《Windows 核心程式設計》,至今受益匪淺。當時也有一系列一邊看原始碼一邊寫下心得的時候,想想,都已經一年以前的事情了。
直到最近,為了控制伺服器在出現異常時不崩潰,(以前是崩潰的時候打Dump),對SEH(windows結構化異常)又進行了進一步的學習,做到了在伺服器出現了異常情況(例如空指標的訪問)時,伺服器打下Dump,並繼續執行,並不崩潰,結合以前也是我寫的監控系統,通知監控客戶端報警,然後就可以去伺服器上取回dump,並分析錯誤,這對伺服器的穩定性有很大的幫助,不管我們對伺服器的穩定性進行了多少工作,作為C++程式,偶爾的空指標訪問,幾乎沒有辦法避免。。。。。。但是,這個工作,對這樣的情況起到了很好的緩衝作用。在這上面工作許久,有點心得,寫下來,供大家分享,同時也是給很久以後的自己分享。
二、 為什麼需要異常
《Windows核心程式設計》第4版第13章開頭部分描述了一個美好世界,即所編寫的程式碼永遠不會執行失敗,總是有足夠的記憶體,不存在無效的指標。。。。但是,那是不存在的世界,於是,我們需要有一種異常的處理措施,在C語言中最常用的(其實C++中目前最常用的還是)是利用錯誤程式碼(Error Code)的形式。
這裡也為了更好的說明,也展示一下Error Code的示例程式碼:
Error Code常用方式:
1.最常用的就是通過返回值判斷了:
比如C Runtime Library中的fopen介面,一旦返回NULL,Win32 API中的CreateFiley一旦返回INVALID_HANDLE_VALUE,就表示執行失敗了。
2.當返回值不夠用(或者攜帶具體錯誤資訊不夠的)時候,C語言中也常常通過一個全域性的錯誤變數來表示錯誤。
比如C Runtime Library中的errno 全域性變數,Win32 API中的GetLastError,WinSock中的WSAGetLastError函式就是這種實現。
既然Error Code在這麼久的時間中都是可用的,好用的,為什麼我們還需要其他東西呢?
這裡可以參考一篇比較淺的文章。《錯誤處理和異常處理,你用哪一個》,然後本人比較欽佩的pongba還有一篇比較深的文章:《錯誤處理(Error-Handling):為何、何時、如何(rev#2)》,看了後你一定會大有收穫。當pongba列出了16條使用異常的好處後,我都感覺不到我還有必要再去告訴你為什麼我們要使用異常了。
但是,這裡在其無法使用異常的意外情況下,(實際是《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》一書中所寫)
一,用異常沒有帶來明顯的好處的時候:比如所有的錯 誤都會在立即呼叫端解決掉或者在非常接近立即呼叫端的地方解決掉。
二,在實際作了測定之後發現異常的丟擲和捕獲導致了顯著的時間開銷:這通常只有兩種情 況,要麼是在內層迴圈裡面,要麼是因為被丟擲的異常根本不對應於一個錯誤。
很明顯,文中列舉的都是完全理論上理想的情況,受制於國內的開發環境,無論多麼好的東西也不一定實用,你能說國內多少地方真的用上了敏捷開發的實踐經驗?這裡作為現實考慮,補充幾個沒有辦法使用異常的情況:
一.所在的專案組中沒有合理的使用RAII的習慣及其機制,比如無法使用足夠多的smart_ptr時,最好不要使用異常,因為異常和RAII的用異常不用RAII就像吃菜不放鹽一樣。這一點在後面論述一下。
二.當專案組中沒有使用並捕獲異常的習慣時,當專案組中認為使用異常是奇技淫巧時不要使用異常。不然,你自認為很好的程式碼,會在別人眼裡不可理解並且作為異類,接受現實。
三、 基礎篇
先回顧一下標準C++的異常用法
1.C++標準異常
只有一種語法,格式類似:
try
{
}
catch()
{
}
經常簡寫為try-catch,當然,也許還要算上throw。格式足夠的簡單。
以下是一個完整的例子:
MyException:
#include < <string>
#include <iostream>
using namespace std;
class MyException : public exception
{
public:
MyException(const char* astrDesc)
{
mstrDesc = astrDesc;
}
string mstrDesc;
};
int _tmain(int argc, _TCHAR* argv[])
{
try
{
throw MyException("A My Exception");
}
catch(MyException e)
{
cout <<e.mstrDesc <<endl;
}
return 0;
}
這裡可以體現幾個異常的優勢,比如自己的異常繼承體系,攜帶足夠多的資訊等等。另外,雖然在基礎篇,這裡也講講C++中異常的語義,
如下例子中,
throwException:
#include <string>
#include <iostream>
using namespace std;
class MyException : public exception
{
public:
MyException(const char* astrDesc)
{
mstrDesc = astrDesc;
}
MyException(const MyException& aoOrig)
{
cout <<"Copy Constructor MyException" <<endl;
mstrDesc = aoOrig.mstrDesc;
}
MyException& operator=(const MyException& aoOrig)
{
cout <<"Copy Operator MyException" <<endl;
if(&aoOrig == this)
{
return *this;
}
mstrDesc = aoOrig.mstrDesc;
return *this;
}
~MyException()
{
cout <<"~MyException" <<endl;
}
string mstrDesc;
};
void exceptionFun()
{
try
{
throw MyException("A My Exception");
}
catch(MyException e)
{
cout <<e.mstrDesc <<" In exceptionFun." <<endl;
e.mstrDesc = "Changed exception.";
throw;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
try
{
exceptionFun();
}
catch(MyException e)
{
cout <<e.mstrDesc <<" Out exceptionFun." <<endl;
throw;
}
return 0;
}
輸出:
Copy Constructor MyException
A My Exception In exceptionFun.
~MyException
Copy Constructor MyException
A My Exception Out exceptionFun.
~MyException
可以看出當丟擲C++異常的copy語義,丟擲異常後呼叫了Copy Constructor,用新建的異常物件傳入catch中處理,所以在函式中改變了此異常物件後,再次丟擲原異常,並不改變原有異常。
這裡我們經過一點小小的更改,看看會發生什麼:
throwAnotherException
#include <string>
#include <iostream>
using namespace std;
class MyException : public exception
{
public:
MyException(const char* astrDesc)
{
mstrDesc = astrDesc;
}
MyException(const MyException& aoOrig)
{
cout <<"Copy Constructor MyException" <<endl;
mstrDesc = aoOrig.mstrDesc;
}
MyException& operator=(const MyException& aoOrig)
{
cout <<"Copy Operator MyException" <<endl;
if(&aoOrig == this)
{
return *this;
}
mstrDesc = aoOrig.mstrDesc;
return *this;
}
~MyException()
{
cout <<"~MyException" <<endl;
}
string mstrDesc;
};
void exceptionFun()
{
try
{
throw MyException("A My Exception");
}
catch(MyException e)
{
cout <<e.mstrDesc <<" In exceptionFun." <<endl;
e.mstrDesc = "Changed exception.";
throw e;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
try
{
exceptionFun();
}
catch(MyException e)
{
cout <<e.mstrDesc <<" Out exceptionFun." <<endl;
throw;
}
return 0;
}
這裡和throwException程式的唯一區別就在於不是丟擲原有異常,而是丟擲改變後的異常,輸出如下:
Copy Constructor MyException
A My Exception In exceptionFun.
Copy Constructor MyException
Copy Constructor MyException
~MyException
~MyException
Changed exception. Out exceptionFun.
~MyException
你會發現連續的兩次Copy Constructor都是改變後的異常物件,這點很不可理解。。。。。。。因為事實上一次就夠了。但是理解C++的Copy異常處理語義就好理解了,一次是用於傳入下一次的catch語句中的,還有一次是留下來,當在外層catch再次throw時,已經丟擲的是改變過的異常物件了,我用以下例子來驗證這點:
throwTwiceException
#include <string>
#include <iostream>
using namespace std;
class MyException : public exception
{
public:
MyException(const char* astrDesc)
{
mstrDesc = astrDesc;
}
MyException(const MyException& aoOrig)
{
cout <<"Copy Constructor MyException" <<endl;
mstrDesc = aoOrig.mstrDesc;
}
MyException& operator=(const MyException& aoOrig)
{
cout <<"Copy Operator MyException" <<endl;
if(&aoOrig == this)
{
return *this;
}
mstrDesc = aoOrig.mstrDesc;
return *this;
}
~MyException()
{
cout <<"~MyException" <<endl;
}
string mstrDesc;
};
void exceptionFun()
{
try
{
throw MyException("A My Exception");
}
catch(MyException e)
{
cout <<e.mstrDesc <<" In exceptionFun." <<endl;
e.mstrDesc = "Changed exception.";
throw e;
}
}
void exceptionFun2()
{
try
{
exceptionFun();
}
catch(MyException e)
{
cout <<e.mstrDesc <<" In exceptionFun2." <<endl;
throw;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
try
{
exceptionFun2();
}
catch(MyException e)
{
cout <<e.mstrDesc <<" Out exceptionFuns." <<endl;
throw;
}
return 0;
}
輸出如下,印證了我上面的說明。
Copy Constructor MyException
A My Exception In exceptionFun.
Copy Constructor MyException
Copy Constructor MyException
~MyException
~MyException
Changed exception. In exceptionFun2.
~MyException
Copy Constructor MyException
Changed exception. Out exceptionFuns.
上面像語言律師一樣的討論著C++本來已經足夠簡單的異常語法,其實簡而言之,C++總是保持著一個上次丟擲的異常用於使用者再次丟擲,並copy一份在catch中給使用者使用。
但是,實際上,會發現,其實原有的異常物件是一直向上傳遞的,只要你不再次丟擲其他異常,真正發生複製的地方在於你catch異常的時候,這樣,當catch時使用引用方式,那麼就可以避免這樣的複製。
referenceCatch
#include <string>
#include <iostream>
using namespace std;
class MyException : public exception
{
public:
MyException(const char* astrDesc)
{
mstrDesc = astrDesc;
}
MyException(const MyException& aoOrig)
{
cout <<"Copy Constructor MyException: " <<aoOrig.mstrDesc <<endl;
mstrDesc = aoOrig.mstrDesc;
}
MyException& operator=(const MyException& aoOrig)
{
cout <<"Copy Operator MyException:" <<aoOrig.mstrDesc <<endl;
if(&aoOrig == this)
{
return *this;
}
mstrDesc = aoOrig.mstrDesc;
return *this;
}
~MyException()
{
cout <<"~MyException" <<endl;
}
string mstrDesc;
};
void exceptionFun()
{
try
{
throw MyException("A My Exception");
}
catch(MyException& e)
{
cout <<e.mstrDesc <<" In exceptionFun." <<endl;
e.mstrDesc = "Changed exception.";
throw;
}
}
void exceptionFun2()
{
try
{
exceptionFun();
}
catch(MyException e)
{
cout <<e.mstrDesc <<" In exceptionFun2." <<endl;
throw;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
try
{
exceptionFun2();
}
catch(MyException e)
{
cout <<e.mstrDesc <<" Out exceptionFuns." <<endl;
throw;
}
return 0;
}
上例中,使用引用方式來捕獲異常,輸出如下:
A My Exception In exceptionFun.
Copy Constructor MyException: Changed exception.
Changed exception. In exceptionFun2.
~MyException
Copy Constructor MyException: Changed exception.
Changed exception. Out exceptionFuns.
~MyException
完全符合C++的引用語義。
基本可以發現,做了很多無用功,因為try-catch無非是一層迷霧,其實這裡複製和引用都還是遵循著原來的C++簡單的複製,引用語義,僅僅這一層迷霧,讓我們看不清楚原來的東西。所以,很容易理解一個地方throw一個物件,另外一個地方catch一個物件一定是同一個物件,其實不然,是否是原來那個物件在於你傳遞的方式,這就像這是個引數,通過catch函式傳遞進來一樣,你用的是傳值方式,自然是通過了複製,通過傳址方式,自然是原有物件,僅此而已。
另外,最終總結一下,《C++ Coding Standards》73條建議Throw by value,catch by reference就是因為本文描述的C++的異常特性如此,所以才有此建議,並且,其補上了一句,重複提交異常的時候用throw;
四、 參考資料
1. Windows核心程式設計(Programming Applications for Microsoft Windows),第4版,Jeffrey Richter著,黃隴,李虎譯,機械工業出版社
2. MSDN—Visual Studio 2005 附帶版,Microsoft
(2) 智慧指標與C++異常
一、 綜述
《異常處理與MiniDump詳解(1) C++異常》稍微回顧了下C++異常的語法及其類似於函式引數傳遞的丟擲異常物件的copy,引用語義,但是有個問題沒有詳細講,那就是C++異常的絕佳搭檔,智慧指標。在沒有智慧指標的時候會感覺C++的異常少了一個用於釋放資源的finally語法,但是C++沒有這樣的語法是有理由的,因為C++的智慧指標。假如不用智慧指標僅僅使用異常,那就像是吃一道沒有放肉的辣椒炒肉一樣。。。。。。。。。。。
智慧指標對於C++的重要性很多人可能並沒有認識到,看看C++相關的書籍吧,幾乎每本都有其痕跡,從《C++ Primer》,TCPL到《C++沉思錄》《C++程式設計藝術》《C++Templates》,Meyes的《Effective C++》中講過,《More Effective C++》再講,無論是概念,用法,實現都是一本一本反覆提起,這樣反覆提起,反覆講解的知識,自然是有其作用的,其作用也很簡單,為了彌補C++對於記憶體管理的不足。眾所周知,C++對於記憶體管理的方式稱為手動管理,說白了就是C++本身不管理,都由程式設計師管理,事實上導致的問題一片一片,不記得多少次反覆的在公司的伺服器程式中去查記憶體洩露了,幾乎隨著每次大規模功能的更新,都會有新的記憶體洩露出現。。。。。C++相信程式設計師的原則告訴我們,程式設計師總是對的,那麼即使是記憶體洩露了,也是有他的理由。。。。。。他的理由就是下一次可以進行記憶體洩露檢查的工作,以浪費一天又一天的時間。隨著BoundsChecker這樣的工具出現,雖然簡化了一部分明顯的記憶體洩露檢查,但是實際上覆雜情況下的記憶體洩露還是隻能靠自己完成,BoundsChecker誤報的本事太強了,並且根本無法正常執行公司的地圖伺服器並退出,可能因為隨後的記憶體洩露正常報告+誤報超出了BoundsChecker的整數上限,總是會將BoundsChecker拉入無響應的狀態。
事實上,我工作中也用的相對較少,畢竟C++標準庫中的auto_ptr是個比較扭曲的東西,不僅使用語義奇怪,碰到稍微複雜的應用就根本勝任不了了,這是很無奈的事情。這一點可以參考我很久以前的文章《C++可憐的記憶體管理機制漫談及奇怪補救auto_ptr介紹》,文中可以看到,在加入了類,異常機制,並且延續著C語言中手動管理記憶體方式的C++中,auto_ptr其實最多也就算種奇異的補丁機制。
但是我們可以求助於boost的智慧指標庫,那裡豐富的資源改變了很多事情,但是我工作中是不允許使用boost庫的。。。。又一次的無奈。
二、 智慧指標
1. 什麼是智慧指標
要知道什麼是智慧指標,首先了解什麼稱為 “資源分配即初始化”這個翻譯的異常扭曲的名詞。RAII—Resource Acquisition Is Initialization,外國人也真有意思,用一個完整的句子來表示一個應該用名詞表示的概念,我們有更有意思了,直接翻譯過來,相當扭曲。。。。。
在《C++ Primer》這樣解釋的,“通過定義一個類來封裝資源的分配和釋放,可以保證正確釋放資源”
而智慧指標就是這種技術的實現,《More Effective C++》中這樣描述的:“Smart pointers are objects that are designed to look,act,and feel like build-in pointers,but to offer greater functionality.They have a variety of applications, including resource management.”
《Effective C++》給出的關鍵特點是:
1. 資源分配後立即由資源管理物件接管。
2. 資源管理物件用解構函式來確保資源被釋放。
基本上,這就是智慧指標的核心概念了,至於智慧指標實現上的特點,比如所有權轉移,所有權獨佔,引用計數等,都是次要的東西了。
目前我見過關於各種智慧指標分類,介紹,使用方法說明最詳細的應該是《Beyond the C++ Standard Library: An Introduction to Boost》一書,此書第一章第一個專題庫就是關於智慧指標的,除了對標準庫中已有的auto_ptr沒有介紹(因為本書是講Boost的嘛),對Boost庫中的智慧指標進行了較為詳細的描述,推薦想了解的都去看看。
文中論及的智慧指標包括
scoped_ptr,scoped_array:所有權限制實現
shared_ptr,shared_array:引用計數實現
intrusive_ptr:引用計數插入式實現
weak_ptr:無所有權實現
關於智慧指標的實現及原理,本人看過最詳細的介紹是在More Effective C++ Items 28,29
這裡僅僅介紹最廣泛使用的智慧指標shared_ptr,加上以前寫過的auto_ptr(《C++可憐的記憶體管理機制漫談及奇怪補救auto_ptr介紹》)給出智慧指標的一些用法示例,其他的智慧指標因為實現上的區別導致使用上也有一些區別,但是核心概念是一樣的,都是上面提及的兩條關鍵特點。
2. shared_ptr介紹
shared_ptr是通過引用計數計數實現的智慧指標,應用也最為廣泛,也是早在TR1就已經確認會進入下一版C++標準的東西,現在我還會因為標準庫中沒有,boost庫不準用而遺憾,過幾年,總有一天,我們就能自由使用類似shared_ptr的指標了。
原型如下:
namespace boost {
template<typename T> class shared_ptr {
public:
template <class Y> explicit shared_ptr(Y* p);
template <class Y,class D> shared_ptr(Y* p,D d);
~shared_ptr();
shared_ptr(const shared_ptr & r);
template <class Y> explicit
shared_ptr(const weak_ptr<Y>& r);
template <class Y> explicit shared_ptr(std::auto_ptr<Y>& r);
shared_ptr& operator=(const shared_ptr& r);
void reset();
T& operator*() const;
T* operator->() const;
T* get() const;
bool unique() const;
long use_count() const;
operator unspecified_bool_type() const;
void swap(shared_ptr<T>& b);
};
template <class T,class U>
shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r);
}
首先,為了檢視資源分配方便,引入一個方便檢視資源轉移,拷貝情況的類:
#include <string>
#include <iostream>
template<class T>
class CResourceObserver
{
public:
CResourceObserver()
{
cout <<typeid(T).name() <<" Construct." <<endl;
}
CResourceObserver(const CResourceObserver& orig)
{
cout <<typeid(T).name() <<" Copy Construct." <<endl;
}
operator=(const CResourceObserver& orig)
{
cout <<typeid(T).name() <<" operator = " <<endl;
}
virtual ~CResourceObserver(void)
{
cout <<typeid(T).name() <<" Deconstruct." <<endl;
}
};
這個類,利用了執行時型別識別及模板,這樣發生與資源有關的操作時,都能通過輸出恰當的反映出來。
shared_ptr的最簡單應用
這裡看個最簡單的shared_ptr使用的例子,順面看看CResourceObserver的使用。在最簡單的一次性使用上,shared_ptr幾乎沒有區別。
例一:
#include <boost/smart_ptr.hpp>
#include "ResourceObserver.h"
using namespace std;
using namespace boost;
class MyClass : public CResourceObserver<MyClass>
{
};
void Fun()
{
shared_ptr<MyClass> sp(new MyClass);
}
int _tmain(int argc, _TCHAR* argv[])
{
cout <<"Fun called." <<endl;
Fun();
cout <<"Fun ended." <<endl;
return 0;
}
輸出如下:
Fun called.
class MyClass Construct.
class MyClass Deconstruct.
Fun ended.
我們只new,並沒有顯式的delete,但是MyClass很顯然也是析構了的。
這裡將shared_ptr替換成auto_ptr也是完全可以的,效果也一樣。
shared_ptr的與auto_ptr的區別
shared_ptr與auto_ptr的區別在於所有權的控制上。如下例:
例二:
#include <boost/smart_ptr.hpp>
#include <memory>
#include "ResourceObserver.h"
using namespace std;
using namespace boost;
class MyClass : public CResourceObserver<MyClass>
{
public:
MyClass() : CResourceObserver<MyClass>()
{
mstr = typeid(MyClass).name();
}
void print()
{
cout <<mstr <<" print" <<endl;
}
std::string mstr;
};
typedef shared_ptr<MyClass> spclass_t;
//typedef auto_ptr<MyClass> spclass_t;
void Fun2(spclass_t& asp)
{
spclass_t sp3(asp);
cout <<asp.use_count() <<endl;
asp->print();
return;
}
void Fun()
{
spclass_t sp(new MyClass);
cout <<sp.use_count() <<endl;
spclass_t sp2(sp);
cout <<sp.use_count() <<endl;
Fun2(sp);
cout <<sp.use_count() <<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
cout <<"Fun called." <<endl;
Fun();
cout <<"Fun ended." <<endl;
return 0;
}
輸出:
Fun called.
class MyClass Construct.
1
2
3
class MyClass print
2
class MyClass Deconstruct.
Fun ended.
此例中將shared_ptr分配的資源複製了3份(實際是管理權的複製,資源明顯沒有複製),每一個shared_ptr結束其生命週期時釋放一份管理權。每一個都有同等的使用許可權。輸出的引用計數數量顯式了這一切。在這裡,可以嘗試替換shared_ptr到auto_ptr,這個程式沒有辦法正確執行。
shared_ptr的引用計數共享所有權
因為沒有拷貝構造及operator=的操作,我們可以知道,物件沒有被複制,為了證實其使用的都是同一個資源,這裡再用一個例子證明一下:
例3:
#include <boost/smart_ptr.hpp>
#include <memory>
#include "ResourceObserver.h"
using namespace std;
using namespace boost;
class MyClass : public CResourceObserver<MyClass>
{
public:
MyClass() : CResourceObserver<MyClass>()
{
mstr = typeid(MyClass).name();
}
void set(const char* asz)
{
mstr = asz;
}
void print()
{
cout <<mstr <<" print" <<endl;
}
std::string mstr;
};
typedef shared_ptr<MyClass> spclass_t;
void Fun2(spclass_t& asp)
{
spclass_t sp3(asp);
sp3->set("New Name");
return;
}
void Fun()
{
spclass_t sp(new MyClass);
spclass_t sp2(sp);
Fun2(sp);
sp->print();
sp2->print();
}
int _tmain(int argc, _TCHAR* argv[])
{
cout <<"Fun called." <<endl;
Fun();
cout <<"Fun ended." <<endl;
return 0;
}
輸出:
Fun called.
class MyClass Construct.
New Name print
New Name print
class MyClass Deconstruct.
Fun ended.
shared_ptr與標準庫容器
在標準庫容器中存入普通指標來實現某個動態繫結的實現是很普遍的事情,但是實際上每次都得記住資源的釋放,這也是BoundsChecker誤報的最多的地方。
例4:
#include <boost/smart_ptr.hpp>
#include <vector>
#include "ResourceObserver.h"
using namespace std;
using namespace boost;
class MyClass : public CResourceObserver<MyClass>
{
public:
MyClass() : CResourceObserver<MyClass>()
{
mstr = typeid(MyClass).name();
}
void set(const char* asz)
{
mstr = asz;
}
void print()
{
cout <<mstr <<" print" <<endl;
}
std::string mstr;
};
typedef shared_ptr<MyClass> spclass_t;
typedef vector< shared_ptr<MyClass> > spclassVec_t;
void Fun()
{
spclassVec_t spVec;
spclass_t sp(new MyClass);
spclass_t sp2(sp);
cout <<sp.use_count() <<endl;
cout <<sp2.use_count() <<endl;
spVec.push_back(sp);
spVec.push_back(sp2);
cout <<sp.use_count() <<endl;
cout <<sp2.use_count() <<endl;
sp2->set("New Name");
sp->print();
sp2->print();
spVec.pop_back();
cout <<sp.use_count() <<endl;
cout <<sp2.use_count() <<endl;
sp->print();
sp2->print();
}
int _tmain(int argc, _TCHAR* argv[])
{
cout <<"Fun called." <<endl;
Fun();
cout <<"Fun ended." <<endl;
return 0;
}
輸出:
Fun called.
class MyClass Construct.
2
2
4
4
New Name print
New Name print
3
3
New Name print
New Name print
class MyClass Deconstruct.
Fun ended.
當在標準庫容器中儲存的是shared_ptr時,幾乎就可以不考慮資源釋放的問題了,該釋放的時候自然就釋放了,當一個資源從一個容器輾轉傳遞幾個地方的時候,常常會搞不清楚在哪個地方統一釋放合適,用了shared_ptr後,這個問題就可以不管了,每次的容器Item的新增增加計數,容器Item的減少就減少計數,恰當的時候,就釋放了。。。。方便不可言喻。
3. 智慧指標的高階應用:
已經說的夠多了,再說下去幾乎就要脫離講解智慧指標與異常的本意了,一些很有用的應用就留待大家自己去檢視資料吧。
1. 定製刪除器,shared_ptr允許通過定製刪除器的方式將其用於其它資源的管理,幾乎只要是通過分配,釋放形式分配的資源都可以納入shared_ptr的管理範圍,比如檔案的開啟關閉,目錄的開啟關閉等自然不在話下,甚至連臨界區,互斥物件這樣的複雜物件,一樣可以納入shared_ptr的管理。
2. 從this建立shared_ptr 。
以上兩點內容在《Beyond the C++ Standard Library: An Introduction to Boost》智慧指標的專題中講解了一些,但是稍感不夠詳細,但是我也沒有看到更為詳細的資料,聊勝於無吧。
3. Pimpl:
《Beyond the C++ Standard Library: An Introduction to Boost》中將智慧指標的時候有提及,在《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》 第43 條Pimpl judiciously中對其使用的好處,壞處,方式等都有較為詳細的講解,大家可以去參考一下,我就不在此繼續獻醜了。
但是,事實上,很多時候並不是只能使用智慧指標(比如pimpl),只是,假如真的你合理的使用了智慧指標,那麼將會更加安全,更加簡潔。
以下是《Beyond the C++ Standard Library: An Introduction to Boost》對shared_ptr使用的建議:
1.當有多個使用者使用同一個物件,而沒有一個明顯的擁有者時
2.當要把指標存入標準庫容器時
3.當要傳送物件到庫或從庫獲取物件,而沒有明確的所有權時
4.當管理一些需要特殊清除方式的資源時
三、 智慧指標與C++異常
因為考慮到大家可能對智慧指標不夠熟悉,所以講到這裡的時候對智慧指標進行了較多的講解,幾乎就脫離主題了,在這裡開始進入正題。
一開始我就講了智慧指標對於C++的異常機制非常重要,到底重要在哪裡呢?這裡看兩個C++異常的例子,一個使用了沒有使用智慧指標,一個使用了智慧指標。
void Fun()
{
MyClass* lp1 = NULL;
MyClass* lp2 = NULL;
MyClass* lp3 = NULL;
lp1 = new MyClass;
try
{
lp2 = new MyClass;
}
catch(bad_alloc)
{
delete lp1;
}
try
{
lp3 = new MyClass;
}
catch(bad_alloc)
{
delete lp1;
delete lp2;
}
// Do Something.....
delete lp1;
delete lp2;
delete lp3;
}
void Fun2()
{
try
{
spclass_t sp1(new MyClass);
spclass_t sp2(new MyClass);
spclass_t sp3(new MyClass);
}
catch(bad_alloc)
{
// No need to delete anything
}
// Do Something
// No need to delete anything
}
區別,好處,明眼人不用看第二眼。這裡為了簡單,用記憶體的作為示例,雖然現在記憶體分配的情況很少見了,但是其他資源原理上是一樣的,個人經驗最深的地方實在Python的C API使用上,哪怕一個簡單的C++,Python函式相互呼叫,都會有N個PyObject*出來,一個一個又一個,直到頭昏腦脹,使用了智慧指標後,簡化的不止一半程式碼。
其實從本質上來講,異常屬於增加了程式從函式退出的路徑,而C++原來的記憶體管理機制要求每個分支都需要手動的釋放每個分配了的資源,這是本質的複雜度,在用於普通return返回的時候,還有一些hack技巧,見《do...while(0)的妙用》,但是異常發生的時候,能夠依賴的就只有手動和智慧指標兩種選擇了。
在沒有智慧指標的光使用異常的時候,甚至會抱怨因為異常增加了函式的出口,導致程式碼的膨脹,說智慧指標是C++異常處理的絕佳搭檔就在於其彌補的此缺點。
另外,其實很多語言還有個finally的異常語法,JAVA,Python都有,SEH也有,其與使用了智慧指標的C++異常比較在劉未鵬關於異常處理的文章《錯誤處理(Error-Handling):為何、何時、如何(rev#2)》中也有詳細描述,我就不在此多費口舌了,將來講SEH的時候自然還會碰到。個人感覺是,有也不錯。。。。畢竟,不是人人都有機會在每個地方都用上智慧指標。
四、 參考資料
1.C++ Primer,中文版第4版,Stanley B.Lippman, Josee lajoie, Barbara E.Moo著 人民郵電出版社
2.Effective C++,Third Edition 英文版,Chapter 3 Resource Management, Scott Meyes著,電子工業出版社
3.More Effective C++(英文版),Scott Meyes著,Items 28,29,機械工業出版社
4.Beyond the C++ Standard Library: An Introduction to Boost,By Bj?rn Karlsson著,Part 1,Library 1,Addison Wesley Professional
5.C++ Coding Standards: 101 Rules, Guidelines, and Best Practices
Herb Sutter, Andrei Alexandrescu著, Addison Wesley Professional
(3) SEH(Structured Exception Handling)
一、 綜述
SEH--Structured Exception Handling,是Windows作業系統使用的異常處理方式。
對於SEH,有點需要說明的是,SEH是屬於作業系統的特性,不為特定語言設計,但是實際上,作為作業系統的特性,幾乎就等同與面向C語言設計,這點很好理解,就像Win32 API,Linux下的系統呼叫,都是作業系統的特性吧,實際還是為C做的。但是,作為為C語言設計的東西,實際上可呼叫的方式又多了,彙編,C++對於呼叫C語言的介面都是比較方便的。
二、 基礎篇
還是簡單介紹一下SEH的使用,但是不準備太詳細的介紹了,具體的詳細介紹見參考中提及的書目。關於SEH的基本應用,《Windows核心程式設計》絕對是最佳讀物(其實個人一直認為《Windows核心程式設計》是Windows程式設計領域必看的第二本書,第一本是《Programming Windows》。關於SEH更深入的一點的知識可能就要參考一些能用匯編講解的書籍了,《Windows使用者態程式高效排錯》算是其中講的不錯的一本。
首先,SEH也有像C++異常一樣的語法,及類try-catch語法,在SEH中為__try-except語法,丟擲異常從throw改為RaiseException,在MSDN中的語法描述為:
__try
{
// guarded code
}
__except ( expression )
{
// exception handler code
}
見一個實際使用的例子:
例1:
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
__try
{
RaiseException(0, 0, 0, NULL);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
cout <<"Exception Raised." <<endl;
}
cout <<"Continue running" <<endl;
}
這可能是最簡單的SEH的例子了,輸出如下:
Exception Raised.
Continue running
這個例子和普通C++異常的try-catch類似,也很好理解。只不過catch換成了except。
因為C語言沒有智慧指標,那麼就不能缺少finally的異常語法,與JAVA,Python等語言中的也類似,(這是C++中沒有的)finally語法的含義就是無論如何(不管是正常還是異常),此句總是會執行,常用於資源釋放。
例2:
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
__try
{
__try
{
RaiseException(0, 0, 0, NULL);
}
__finally
{
cout <<"finally here." <<endl;
}
}
__except(1)
{
}
__try
{
__try
{
int i;
}
__finally
{
cout <<"finally here." <<endl;
}
}
__except(1)
{
}
cout <<"Continue running" <<endl;
getchar();
}
這個例項看起來過於奇怪,因為沒有將各個try-finally放入獨立的模組之中,但是說明了問題:
1. finally的語句總是會執行,無論是否異常finally here總是會輸出。
2. finally僅僅是一條保證finally語句執行的塊,並不是異常處理的handle語句(與except不同),所以,假如光是有finally語句塊的話,實際效果就是異常會繼續向上丟擲。(異常處理過程也還是繼續)
3. finally執行後還可以用except繼續處理異常,但是SEH奇怪的語法在於finally與except無法同時使用,不然會報編譯錯誤。
如下例:
__try
{
RaiseException(0, 0, 0, NULL);
}
__except(1)
{
}
__finally
{
cout <<"finally here." <<endl;
}
VS2005會報告
error C3274: __finally 沒有匹配的try
這點其實很奇怪,難道因為SEH設計過於老了?-_-!因為在現在的語言中finally都是允許與except(或類似的塊,比如catch)同時使用的。C#,JAVA,Python都是如此,甚至在MS為C++做的託管擴充套件中都是允許的。如下例:(來自MSDN中對finally keyword [C++]的描述)
using namespace System;
ref class MyException: public System::Exception{};
void ThrowMyException() {
throw gcnew MyException;
}
int main() {
try {
ThrowMyException();
}
catch ( MyException^ e ) {
Console::WriteLine( "in catch" );
Console::WriteLine( e->GetType() );
}
finally {
Console::WriteLine( "in finally" );
}
}
當你不習慣使用智慧指標的時候常常會覺得這樣會很好用。關於finally異常語法和智慧指標的使用可以說是各有長短,這裡提供劉未鵬的一種解釋,(見參考5的RAII部分,文中比較的雖然是JAVA,C#,但是實際SEH也是類似JAVA的)大家參考參考。
SEH中還提供了一個比較特別的關鍵字,__leave,MSDN中解釋如下
Allows for immediate termination of the __try block without causing abnormal termination and its performance penalty.
簡而言之就是類似goto語句的丟擲異常方式,所謂的沒有效能損失是什麼意思呢?看看下面的例子:
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
int i = 0;
__try
{
__leave;
i = 1;
}
__finally
{
cout <<"i: " <<i <<" finally here." <<endl;
}
cout <<"Continue running" <<endl;
getchar();
}
輸出:
i: 0 finally here.
Continue running
實際就是類似Goto語句,沒有效能損失指什麼?一般的異常丟擲也是沒有效能損失的。
MSDN解釋如下:
The __leave keyword
The __leave keyword is valid within a try-finally statement block. The effect of __leave is to jump to the end of the try-finally block. The termination handler is immediately executed. Although a goto statement can be used to accomplish the same result, a goto statement causes stack unwinding. The __leave statement is more efficient because it does not involve stack unwinding.
意思就是沒有stack unwinding,問題是。。。。。。如下例,實際會導致編譯錯誤,所以實在不清楚到__leave到底幹啥的,我實際中也從來沒有用過此關鍵字。
#include <iostream>
#include <windows.h>
using namespace std;
void fun()
{
__leave;
}
int main()
{
__try
{
fun();
}
__finally
{
cout <<" finally here." <<endl;
}
cout <<"Continue running" <<endl;
getchar();
}
三、 提高篇
1. SEH的優點
1) 一個很大的優點就是其對異常程序的完全控制,這一點是C++異常所沒有的,因為其遵循的是所謂的終止設定。
這一點是通過except中的表示式來控制的(在前面的例子中我都是用1表示,實際也就是使用了EXCEPTION_EXECUTE_HANDLER方式。
EXCEPTION_CONTINUE_EXECUTION (–1) 表示在異常發生的地方繼續執行,表示處理過後,程式可以繼續執行下去。 C++中沒有此語義。
EXCEPTION_CONTINUE_SEARCH (0) 異常沒有處理,繼續向上丟擲。類似C++的throw;
EXCEPTION_EXECUTE_HANDLER (1) 異常被處理,從異常處理這一層開始繼續執行。 類似C++處理異常後不再丟擲。
2) 作業系統特性,不僅僅意味著你可以在更多場合使用SEH(甚至在組合語言中使用),實際對異常處理的功能也更加強大,甚至是程式的嚴重錯誤也能恢復(不僅僅是一般的異常),比如,除0錯誤,訪問非法地址(包括空指標的使用)等。這裡可以用一個例子來說明:
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
__try
{
int *p = NULL;
*p = 0;
}
__except(1)
{
cout <<"catch that" <<endl;
}
cout <<"Continue running" <<endl;
getchar();
}
輸出:
catch that
Continue running
在C++中這樣的情況會導致程式直接崩潰的,這一點好好利用,可以使得你的程式穩定性大增,以彌補C++中很多的不足。但是,問題又來了,假如異常都被這樣處理了,甚至沒有聲息,非常不符合發生錯誤時死的壯烈的錯誤處理原則。。。。。。。很可能導致程式一堆錯誤,你甚至不知道為什麼,這樣不利於發現錯誤。
但是,SEH與MS提供的另外的特性MiniDump可以完美的配合在一起,使得錯誤得到控制,但是錯誤情況也能捕獲到,稍微的緩解了這種難處(其實也說不上完美解決)。
這一點需要使用者自己權衡,看看到底開發進入了哪個階段,哪個更加重要,假如是伺服器程式,那麼在正式跑著的時候,每崩潰一次就是實際的損失。。。所以在後期可以考慮用這種方式。
關於這方面的資訊,在下一次在詳細講解。
2. SEH的缺點
其實還是有的,因為是為作業系統設計的,實際類似為C設計,那麼,根本就不知道C++中類/物件的概念,所以,實際上不能識別並且正確的與C++類/物件共存,這一點使用C++的需要特別注意,比如下例的程式根本不能通過編譯。
例一:
int main()
{
CMyClass o;
__try
{
}
__except(1)
{
cout <<"catch that" <<endl;
}
cout <<"Continue running" <<endl;
getchar();
}
例二:
int main()
{
__try
{
CMyClass o;
}
__except(1)
相關推薦
異常處理與MiniDump詳解【轉帖】
(1) C++異常 一、 綜述 我很少敢為自己寫的東西弄個詳解的標題,之所以這次敢於這樣,自然還算是有點底氣的。並且也以此為動力,督促自己好好的將這兩個東西研究透。 當年剛開始工作的時候,第一個工作就是學習breakpad的原始碼,然後瞭解其原理,為公司寫一個ExceptionHandle的庫,以處理伺
異常處理與MiniDump詳解 1 C++異常
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
異常處理與MiniDump詳解 4 MiniDump
程式崩潰的問題解決了,問題是,有很多時候,很多程式是不允許隨便崩潰的,這樣,在程式崩潰後再去發現問題就有些晚了,那麼,有沒有程式不崩潰時也能發現問題的方法呢?前面描述的SEH就是一種讓程式不崩潰的方法,不過在那種方式下,按以前描述的方法,崩潰是不崩潰了,但是實際上,掩蓋了很多問題,對於問題的發現有些不利的地方
異常處理與MiniDump詳解-(3)SHE(Structured Exception Handling)
The __leave keyword is valid within a try-finally statement block. The effect of __leave is to jump to the end of the try-finally block. The termination ha
es6 map()和filter()詳解【轉】
低版本 window get 簡約 push foreach 沒有 數值 length 原文地址:http://www.zhangxinxu.com/wordpress/2013/04/es5%e6%96%b0%e5%a2%9e%e6%95%b0%e7%bb%84%e
二分查找的平均查找長度詳解【轉】
nbsp 得到 所有 二分查找 次數 log 來源 分析 blog 來源:http://blog.csdn.net/turne/article/details/50488378 看數據結構書的時候碰上的內容,我自己將它化成關於級數的題,然後自己算的過程,基本就是等比級數和
Linux "ls -l"文件列表權限詳解 【轉】
所有者 target tac 文件的 追加 amp sage ble named 1.使用 ls -l 命令 執行結果如下(/var/log) :drwxr-x--- 2 root adm 4096 2013-08-07 11:03 apac
Linux Samba服務主配文件smb.conf中文詳解【轉】
except 共享資源 參考 -s 開啟 eve crypt 詳解 pat 轉自:https://blog.csdn.net/maotianwang/article/details/52524732 從網上找到描述比較詳細的smb.conf中文解釋: 服務
【python】 time模塊和datetime模塊詳解 【轉】
-a cond .com ima 封裝 基本 sta times %d 一、time模塊 time模塊中時間表現的格式主要有三種: a、timestamp時間戳,時間戳表示的是從1970年1月1日00:00:00開始按秒計算的偏移量 b、struct_time時間
Linux htop工具使用詳解【轉】
進程信息 保存 作用 ctrl+c 設置 uri ppi 虛擬 獲取 原文地址: http://www.cnphp6.com/archives/65078 一.Htop的使用簡介 大家可能對top監控軟件比較熟悉,今天我為大家介紹另外一個監控軟件Htop,姑且稱之為top的
Nginx安裝及配置詳解【轉】
nginx概述 nginx是一款自由的、開源的、高效能的HTTP伺服器和反向代理伺服器;同時也是一個IMAP、POP3、SMTP代理伺服器;nginx可以作為一個HTTP伺服器進行網站的釋出處理,另外nginx可以作為反向代理進行負載均衡的實現。 這裡主要通過三個方面簡單介紹nginx
MySQL資料庫的鎖詳解【轉】
當然在我們的資料庫中也有鎖用來控制資源的併發訪問,這也是資料庫和檔案系統的區別之一。 為什麼要懂資料庫鎖? 通常來說對於一般的開發人員,在使用資料庫的時候一般懂點 DQL(select),DML(insert,update,delete)就夠了。 小明是一個剛剛畢業在網際網路公司工作的 Java 開發工
c++迭代器(iterator)詳解【轉】
(轉自:https://www.cnblogs.com/hdk1993/p/4419779.html) 1. 迭代器(iterator)是一中檢查容器內元素並遍歷元素的資料型別。 (1) 每種容器型別都定義了自己的迭代器型別,如vector: vector<int>::it
Linux裝置樹語法詳解【轉】
轉自:https://www.cnblogs.com/xiaojiang1025/p/6131381.html 概念 Linux核心從3.x開始引入裝置樹的概念,用於實現驅動程式碼與裝置資訊相分離。在裝置樹出現以前,所有關於裝置的具體資訊都要寫在驅動裡,一旦外圍裝置變化,驅動程式碼就要重寫。引入了裝置樹之
png檔案格式詳解【轉】
5.2.2 PNG影象檔案儲存結構(1) PNG檔案儲存結構的格式可以在http://www.w3.org/TR/REC-png.htm上找到定義。 BMP檔案總體上由兩部分組成,分別是PNG檔案標誌和資料塊(chunks),如表5-8所示。其中資料塊分為兩類:關鍵資料塊(cri
NAT 詳解【轉】
(轉自:https://blog.csdn.net/freeking101/article/details/77962312) From:http://wwwcisco.blog.51cto.com/218089/39837 CCNA學習筆記之NAT:http://sweetpota
eMMC分割槽詳解【轉】
本文轉載自:https://blog.csdn.net/wxh0000mm/article/details/77864002 轉自:http://blog.csdn.net/junzhang1122/article/details/48142529
刨根問底 | Elasticsearch 5.X叢集多節點角色配置深入詳解【轉】
轉自:https://blog.csdn.net/laoyang360/article/details/78290484 1、問題引出 ES5.X節點型別多了ingest節點型別。 針對3個節點、5個節點或更多節點的叢集,如何配置節點角色才能使得系統性能最優呢? 2、ES2.X及之前版本節點角色概述 3、
ELK & ElasticSearch 5.1 基礎概念及配置檔案詳解【轉】
轉自:https://blog.csdn.net/zxf_668899/article/details/54582849 1. 配置檔案 elasticsearch/elasticsearch.yml 主配置檔案 elasticsearch/jvm.options jvm引數配置檔案
mysql中left join,right join,inner join,outer join的用法詳解【轉】
非常慚愧用了這麼久的mysql居然沒有用過outer join和inner join,對outer join的認識也僅是知道它是外連結,至於什麼用途都不清楚,至於還有沒有left outer join更是不得而知,某天有人問起,才想起自己mysql知識的貧乏,趕緊找了一下網上