最近兩年C用得多了,C++有些生疏,又常常用PYTHON,或者閱讀些JAVA的代碼,感覺C的開發者們由於C語言在軟件工程上的先天缺陷,導致開發效率不高,所以決定拿出C++來看看用用,准備把libevent封裝出一個類ACE的C++實現,首先來復讀下C++對象模型吧。
如果要研究C++的對象模型,大家潛意識都想知道的是,C++比C好在哪裡?又比C差在哪裡?
我們主要就是想從C++的對象模型裡找到後一個答案。前一個答案在軟件工程中是毫無疑義的,面向對象的優越性要比C語言裡一堆數據結構+和一堆可能與它們相關的函數,可讀性、可用性好很好,對開發大型軟件工程,需要幾百人開發一個項目來說,C++好太多了。看看JAVA或者python程序員們,他們為什麼可以一直站在巨人的肩膀上,想完成任何一個功能都超級方便的調用大師們以前寫好的package/API,借用各種設計模式,應用級別程序員們可以非常EASY的使用復雜的設計,一些只有高級C程序員才能掌握的東東。當然,JAVA的很多特性也導致不適應核心服務器的開發,比如它的垃圾回收機制。
OK,閒話少敘,在看對象模型前,先看幾個C++與C語言的典型不同之處。
1、自然是類的定義了,最大的改變就是類把數據結構與方法捆到一起了,可讀性上提升巨大。對成員變量和成員方法,有5種類型:static member, nonstatic member, static function, nonstatic function, virtual function.
2、繼承,這裡很有許多細節了,核心解決問題就是動態綁定,也就是virtual關鍵字。virtual出現的唯一原因就是為了解決繼承機制,否則struct裡引入方法就足夠了,class出現就是為了這。virtual關鍵字解決了子類實例和父類實例的一些特殊關系,考慮以下場景:軟件工程中,很喜歡每個模塊專注於自己的事,盡量忽略與自己無關的實現,這樣,很可能會用一個父類指針,該指針太可能指向多種不同的子類了,但是現在,使用這個抽象父類指針的模塊不想關注細節,當它調用對象的某個方法時,到底是調用父類的方法還是子類的方法呢?動態綁定這個特性就是,開發者可以決定這一點,當你用virtual關鍵字申明父類方法時,如果子類重定義了該方法,如果這個指針實際指向的是某子類對象,那麼調用的方法一定是該子類方法的實現。
舉個例子吧,就像什麼析構函數總喜歡寫成virtaul?這個例子應該容易說明virtual的玩法。一段簡單的代碼:
#include <iostream>
using namespace std;
class Father
{
public:
int m_fMember;
Father(){m_fMember=1;}
~Father(){cout<<m_fMember<<endl;}
};
class Child : public Father{
public:
int m_cMember;
Child(){m_cMember=2;}
~Child(){cout<<m_cMember<<endl;}
};
int main(int argc, char** argv)
{
Father* pObj1 = new Child();
delete pObj1;
Child* pObj2 = new Child();
delete pObj2;
return 0;
}
這段代碼的結果是1 2 1,啥意思呢?就是說,如果不用virtual函數,是沒有執行期綁定一說的,比如pObj1這個指針,其實它是Child對象,但是在釋放時,~Child()方法並沒有被調用,僅調用了~Father方法。為什麼呢?因為沒有用virtual,就是編譯期綁定,當你在編譯時gcc/g++只知道pObj1是個Father對象,所以在delete時就去調用Father的析構了。而如果定義成virtual ~Father時,結果就是一定會析構Child,這就是為什麼析構函數都要用virtual,因為沒人知道會不會有子類繼承,否則一旦繼承,發生這樣的事,析構函數裡萬一釋放了些資源,比如SOCKET,比如memory,那就是資源洩露了。www.2cto.com
那麼以上,C++對象模型是怎麼做到的呢?畫張圖吧。先定義一個類,再看看它的內存布局:
class Father
{
public:
int m_fMember;
static int m_sMember;
static void testSFunc(){}
void testFunc(){}
virtual void testVFunc(){}
Father(){m_fMember=1;}
virtual ~Father(){cout<<m_fMember<<endl;}
};
加工了下上面的例子,看看它的實例的內存布局是啥樣的:
這裡大家明白了吧?即使一個Child對象在編譯時被賦為Father類型,但是實際調用時,virtual方法會被單獨的拎出來,在vtbl中指向實際的實現,所以,該對象在delete時會調用Child的析構函數,而如果你像上面例子那樣,析構方法不使用virtual,將會用到上圖中的最後一個指針,指向類成員函數裡,這樣就不是執行期綁定了。
剩下的static成員,都是與對象實例無關的內存布局。這樣,其實如果不使用virtual,C++比之C並沒有增加成本,盡可放心使用。
摘自 russell_tao的專欄