歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 生成一個C++對象的成本

生成一個C++對象的成本

日期:2017/3/1 10:34:45   编辑:Linux編程
C用得多了,C++有些生疏,又常常用PYTHON,或者閱讀些JAVA的代碼,感覺C的開發者們由於C語言在軟件工程上的先天缺陷,導致開發效率不高,所以決定拿出C++來看看用用,准備把libevent封裝出一個類ACE的C++實現,首先來復讀下C++對象模型吧。要了解new一個object的成本,最主要的就是知道,編譯器會給對象分配多少內存,知道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關鍵字申明父類方法時,如果子類重定義了該方法,如果這個指針實際指向的是某子類對象,那麼調用的方法一定是該子類方法的實現。

舉個例子吧,就像什麼析構函數總喜歡寫成virtual?這個例子應該容易說明virtual的玩法。一段簡單的代碼:

[cpp]
  1. #include <iostream>
  2. using namespace std;
  3. class Father
  4. {
  5. public:
  6. int m_fMember;
  7. Father(){m_fMember=1;}
  8. ~Father(){cout<<m_fMember<<endl;}
  9. };
  10. class Child : public Father{
  11. public:
  12. int m_cMember;
  13. Child(){m_cMember=2;}
  14. ~Child(){cout<<m_cMember<<endl;}
  15. };
  16. int main(int argc, char** argv)
  17. {
  18. Father* pObj1 = new Child();
  19. delete pObj1;
  20. Child* pObj2 = new Child();
  21. delete pObj2;
  22. return 0;
  23. }
這段代碼的結果是1 2 1,啥意思呢?就是說,如果不用virtual函數,是沒有執行期綁定一說的,比如pObj1這個指針,其實它是Child對象,但是在釋放時,~Child()方法並沒有被調用,僅調用了~Father方法。為什麼呢?因為沒有用virtual,就是編譯期綁定,當你在編譯時gcc/g++只知道pObj1是個Father對象,所以在delete時就去調用Father的析構了。而如果定義成virtual ~Father時,結果就是一定會析構Child,這就是為什麼析構函數都要用virtual,因為沒人知道會不會有子類繼承,否則一旦繼承,發生這樣的事,析構函數裡萬一釋放了些資源,比如SOCKET,比如memory,那就是資源洩露了。


那麼以上,C++對象模型是怎麼做到的呢?畫張象征性的圖吧。先定義一個類,再看看它的內存布局:

[cpp]
  1. class Father
  2. {
  3. public:
  4. int m_fMember;
  5. static int m_sMember;
  6. static void testSFunc(){}
  7. void testFunc(){}
  8. virtual void testVFunc(){}
  9. Father(){m_fMember=1;}
  10. virtual ~Father(){cout<<m_fMember<<endl;}
  11. };

我們生成一個Father對象,看看它的的內存布局是啥樣的(同志們,這只是近似存儲布局圖,沒有把編譯和運行的差別放上去,下篇再講這個):


這裡大家明白了吧?即使一個Child對象在編譯時被賦為Father類型,但是實際調用時,virtual方法會被單獨的拎出來,在vtbl中指向實際的實現,所以,該對象在delete時會調用Child的析構函數,而如果你像上面例子那樣,析構方法不使用virtual,將會用到上圖中的最後一個指針,指向類成員函數裡,這樣就不是執行期綁定了。


剩下的static成員(還有所有的正常成員函數),都是與對象實例無關的內存布局。這樣,其實如果不使用virtual,C++比之C並沒有增加成本,盡可放心使用。

Copyright © Linux教程網 All Rights Reserved