C++ 對象是怎麼死的?進程篇[轉]

 

我承認這個帖子的名稱有標題黨的嫌疑,但是暫時想不出更好的名稱了,只好先這樣了 :-(
  由於前天的帖子聊了架構設計的多進程問題,所以今天想起來要聊一下和“C++進程終止”相關的那些事。與前幾個C++帖子的風格類似,今天聊的內容,儘量侷限於標準C++範疇,儘量不涉及特定的操作系統平臺。

  ★關於進程的三種死法
  由於今天講的是“進程篇”,自然得先搞明白進程的幾種死法。其實進程和大活人一樣,也有三種死法,分別是“自然死亡、自殺、它殺”。這三種死亡方式具體如下:
  1、自然死亡
  望文生義,自然死亡就是最自然的進程退出方法。具體表現爲通過return語句結束main函數。由於這種方法最優雅(後面會說),如果沒有其它特殊原因,強烈建議採用這種死法。
  2、自殺
   所謂的自殺,就是進程自己調用某些API來自行了斷。在標準C++中,這幾個函數(exit、abort、terminate、unexpected) 可以用於進程自殺。如果沒有額外設置,unexpected函數默認會調用terminate函數,terminate函數默認會調用abort函數。所 以自殺的方式基本上也就是exit和abort兩種。exit相對abort來說溫和一些,所以下文稱之爲溫和自殺;相對地,把abort稱爲激進自殺。
  3、它殺
  它殺其實也挺好理解,就是當前進程被其它進程殺死。標準C++沒有提供用於它殺的API函數,因此常用的方法是通過某些跨平臺的庫(如ACE)提供的API函數或者調用某些外部命令(如Posix系統的kill命令)來實現。
  上面說了這幾種死法,有同學要問了:進程不同的死法和C++對象有什麼關係捏?其實關係大大滴,請聽我細細道來。

  ★類對象的析構(銷燬)
  首先把類對象分爲三種:局部非靜態對象、局部靜態對象、全局對象(尚不清楚這幾種對象差異的同學,請先找本C++入門書拜讀一下)。進程不同的死法對於這幾種對象是否能銷燬會有很大的影響。請看如下的對照表:
------------------------------
        局部非靜態對象  局部靜態對象  全局對象
  自然死亡    能        能      能
  溫和自殺    不能       能      能
  激進自殺    不能       不能     不能
   它殺     不能       不能     不能
------------------------------
  從這個對照表可以看出,激進自殺和它殺的效果類似(各種類對象都無法正常銷燬)。所以我們在寫程序時要極力避免上述這兩種情況。
  另外,溫和自殺也有不爽之處:不能正確地銷燬局部非靜態對象。準確地說,應該是:在調用exit之前已經構造但是尚未析構的局部非靜態對象將再也不會被析構。所以溫和自殺也要避免使用。
  綜上所述,最正經、最靠譜的死法就是第一種:自然死亡。

  ★析構的順序
  那麼,是不是隻要讓進程自然死亡就萬事大吉了?非也!即使所有的類對象都會被析構,還有另一個棘手的問題:析構的順序。先來看下面一個例子:


class CFoo
{
public:
CFoo()
{
cout << "CFoo" << endl;
}
virtual ~CFoo()
{
cout << "~CFoo" << endl;
}
};


  上述示例挺簡單的(有效代碼僅6行),大夥兒能看出有什麼問題嗎?如果你一眼就看出問題之所在,恭喜你,後面的內容你不用看了。
  在C++標準中並沒有規定全局對象和靜態對象的析構順序。由於cout本身是一個全局對象,假如CFoo類也定義了一個全局對象g_foo。當g_foo析構的時候,cout對象可能已經先死了(取決於具體的環境)。在這種情況下,CFoo析構函數的打印語句由於引用了已死的對象,可能會導致不可預料的後果。
  從上面的例子可以看出,如果你在程序中使用了全局對象或者靜態對象,那你要非常小心地編寫相關class/struct的析構函數代碼,儘量不要在它們的析構函數中引用其它的全局對象或靜態對象。
  另外,在C++經典名著《Modern C++ Design》的第6章詳細描述了關於單鍵(Singleton)銷燬的一些細節、場景及解決方法。大夥兒可以去拜讀一下。
  下一個帖子,會聊一下和線程有關的C++對象是怎麼死的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章