C++ virtual 析構函數

copy自:http://zxjgoodboy.blog.sohu.com/61482463.html

在此基礎上稍作修改

?

C++中虛析構函數的作用

我們知道,用C++開發的時候,用來做基類的類的析構函數一般都是虛函數。可是,為什么要這樣做呢?下面用一個小例子來說明:?

有下面的兩個類:

[cpp] view plaincopy
  1. #include?<iostream>??
  2. using?namespace?std;??
  3. class?ClxBase??
  4. {??
  5. public:??
  6. ????ClxBase()?{};??
  7. ????virtual?~ClxBase()?{cout<<"AAA"<<endl;};??
  8. ????virtual?void?DoSomething()?{?cout?<<?"Do?something?in?class?ClxBase!"?<<?endl;?};??
  9. };??
  10. class?ClxDerived?:?public?ClxBase??
  11. {??
  12. public:??
  13. ????ClxDerived()?{};??
  14. ????~ClxDerived()?{?cout?<<?"Output?from?the?destructor?of?class?ClxDerived!"?<<?endl;?};??
  15. ????void?DoSomething()?{?cout?<<?"Do?something?in?class?ClxDerived!"?<<?endl;?};??
  16. };??
  17. int?main()??
  18. {??
  19. ????ClxBase?*pTest?=?new?ClxDerived;??
  20. ????pTest->DoSomething();??
  21. ????delete?pTest;??
  22. } ?

輸出結果:

Do something in class ClxDerived!

Output from the destructor of class ClxDerived!

AAA

?這個很簡單,非常好理解。

但是,如果把類ClxBase析構函數前的virtual去掉,那輸出結果就是下面的樣子了:

Do something in class ClxDerived!

AAA

也就是說,類ClxDerived的析構函數根本沒有被調用!一般情況下類的析構函數里面都是釋放內存資源,而析構函數不被調用的話就會造成內存泄漏。我想所有的C++程序員都知道這樣的危險性。當然,如果在析構函數中做了其他工作的話,那你的所有努力也都是白費力氣。
????所以,文章開頭的那個問題的答案就是--這樣做是為了當用一個基類的指針刪除一個派生類的對象時,派生類的析構函數會被調用。
????當然,并不是要把所有類的析構函數都寫成虛函數。因為當類里面有虛函數的時候,編譯器會給類添加一個虛函數表,里面來存放虛函數指針,這樣就會增加類的存儲空間。所以,只有當一個類被用來作為基類的時候,才把析構函數寫成虛函數。

?

你需要virtual析構函數嗎

使用VC的class wizard自動生成一個類,會得到兩個空的函數:構造函數和virtual析構函數。為什么析構函數要聲明成virtual呢?

如果一個類要被使用成多態(polymorphic)的,那么這個virtual是必須的。比如:

[cpp]?view plaincopy
  1. #include?<iostream>??
  2. class?Animal??
  3. {??
  4. ??char*?ap;??
  5. public:??
  6. ???
  7. ??Animal()??
  8. ??{??
  9. ????ap?=?new?char;??
  10. ????std::cout?<<?"Animal?ctor"?<<?std::endl;??
  11. ??}??
  12. ??virtual?void?foo()??
  13. ??{??
  14. ????std::cout?<<?"Animal::foo"?<<?std::endl;??
  15. ??}??
  16. ??virtual?~Animal()??
  17. ??{??
  18. ????std::cout?<<?"Animal?dtor"?<<?std::endl;??
  19. ????delete?ap;??
  20. ??}??
  21. };??
  22. class?Dog?:?public?Animal??
  23. {??
  24. ??char*?dp;??
  25. public:??
  26. ??Dog()??
  27. ??{??
  28. ????dp?=?new?char;??
  29. ????std::cout?<<?"Dog?ctor"?<<?std::endl;??
  30. ??}??
  31. ??virtual?void?foo()??
  32. ??{??
  33. ????std::cout?<<?"Dog::foo"?<<?std::endl;??
  34. ??}??
  35. ??virtual?~Dog()??
  36. ??{??
  37. ????delete?dp;??
  38. ????std::cout?<<?"Dog?dtor"?<<?std::endl;??
  39. ??}??
  40. };??
  41. int?main()??
  42. {??
  43. ??Animal*?pa?=?new?Dog();??
  44. ??pa->foo();??
  45. ??delete?pa;??
  46. ??return?0;??
  47. }??
?

?

delete pa 實際上相當于:
?pa->~Animal();
?釋放pa所指向的內存(或許是free(pa))。
在 這里,因為~Animal()是virtual的,盡管是通過Animal類型的指針調用的,根據v-table的信息,~Dog()被正確調用到。如果 把virtual屬性去掉,那么被調用的是~Animal(),Dog類的構造函數被調用而析構函數未被調用,構造函數中分配的資源沒有釋放,從而產生了 內存泄漏。析構函數缺省聲明為virtual,就可以避免這一問題。

可另一個問題是,有時virtual是不需要的。如果一個類不會被繼承,比如一個utility類,該類完全是靜態方法;或者一些類盡管可能會被繼承,但不會被使用成多態的,即除了析構函數外,沒有其他的方法是virtual的,這時就可以把virtual屬性去掉。

去掉析構函數的virtual屬性后,因為該類中沒有其他的virtual函數,所以編譯時不會生成v-table,這樣就節省了編譯時間,并減少了最終生成的程序的大小。更重要的是,遵從這一規則,給該類的維護者一個信息,即該類不應被當作多態類使用。

同樣,當作一個抽象時,如果你模仿Java的interface,聲明了如下的虛基類:

[cpp]?view plaincopy
  1. class?AbstractBase??
  2. {??
  3. ?virtual?method1()?=?0;??
  4. ?virtual?method2()?=?0;??
  5. };??
?

?

那么應該給它增加一個空的virtual析構函數:
?virtual ~AbstractBase(){}

如果你對COM比較熟悉,可能會注意到,COM interface中并沒有這個virutal構造函數。這是因為,COM通過使用引用計數的機制來維護對象。當你使用完一個COM對象,調用Release()時,COM的內部實現檢查引用技術是否為零。如果是,則調用
?delete this;
因為Release()是virtual的,所以該COM對象對應的正確的派生類被調用,delete this會調用正確的析構函數,達到了使用virtual析構函數的效果。

?

?

定義純虛析構函數(pure virtual destructor)zz

純虛成員函數通常沒有定義;它們是在抽象類中聲明,然后在派生類中實現。比如說下面的例子:

[cpp]?view plaincopy
  1. class?File?//an?abstract?class??
  2. {??
  3. public:??
  4. ?virtual?int?open(const?string?&?path,?int?mode=0x666)=0;??
  5. ?virtual?int?close()=0;??
  6. //...??
  7. };???
?

但是,在某些情況下,我們卻需要定義一個純虛成員函數,而不僅僅是聲明它。最常見的例子是純虛析構函數。在聲明純虛析構函數時,不要忘了同時還要定義它。

[cpp]?view plaincopy
  1. class?File?//abstract?class??
  2. {??
  3. public:??
  4. ?virtual?~File()=0;?//declaration?of?a?pure?virtual?dtor??
  5. };??
  6. File::~File()?{}?//definition?of?dtor???
?

?

為什么說定義純虛析構函數是非常重要的

?

派生類的析構函數會自動調用其基類的析構函數。這個過程是遞歸的,最終,抽象類的純虛析構函數也會被調用。

如果純虛析構函數只被聲明而沒有定義,那么就會造成運行時(runtime)崩潰。(在很多情況下,這個錯誤會出現在編譯期,但誰也不擔保一定會是這樣。)純虛析構函數的啞元實現(dummy implementation,即空實現)能夠保證這樣的代碼的安全性。

[cpp] view plaincopy
  1. class?DiskFile?:?public?File??
  2. {??
  3. public:??
  4. ?int?open(const?string?&?pathname,?int?mode);??
  5. ?int?close();??
  6. ?~DiskFile();??
  7. };??
  8. File?*?pf?=?new?DiskFile;??
  9. //.?.?.??
  10. delete?pf;?//OK,?ultimately?invokes?File::~File()???

在某些情況下定義其它純虛成員函數可能也是非常有用的(比如說在調試應用程序以及記錄應用程序的日志時)。例如,在一個不應該被調用,但是由于一個缺陷而被調用的基類中,如果有一個純虛成員函數,那么我們可以為它提供一個定義。

[cpp] view plaincopy
  1. class?Abstract??
  2. {??
  3. public:??
  4. ?virtual?int?func()=0;??
  5. //..??
  6. };??
  7. int?Abstract::func()??
  8. {??
  9. std::cerr<<"got?called?from?thread?"?<<?thread_id<<??
  10. ?????????????"at:?"<<gettimeofday()<<std::endl;??
  11. }???

這樣,我們就可以記錄所有對純虛函數的調用,并且還可以定位錯誤代碼;不為純虛函數提供定義將會導致整個程序無條件地終止。


虛構造函數(virtual constructor)

C++不支持直接的虛構造函數。虛 擬機制的設計目的是使程序員在不完全了解細節(比如只知該類實現了某個界面,而不知該類確切是什么東東)的情況下也能使用對象。但是,要建立一個對象,可 不能只知道“這大體上是什么”就完事——你必須完全了解全部細節,清楚地知道你要建立的對象是究竟什么。所以,構造函數當然不能是虛的了。但是,可通過虛函數?virtual clone()(對于拷貝構造函數)或虛函數?virtual create()(對于默認構造函數),得到虛構造函數產生的效果。

注意:子類成員函數clone()的返回值類型故意與父類成員函數clone()的不同。這種特征被稱為“協變的返回類型”(Covariant Return Types),該特征最初并不是C++語言的一部分,VC6.0以下版本編譯器不支持這樣的寫法。

虛析構函數(virtual destructor)

當你可能通過基類指針刪除派生類對象時,建議使用虛析構函數。虛函數綁定到對象的類的代碼,而不是指針/引用的類。如果基類有虛析構函數,delete basePtr(基類指針)時,*basePtr?的對象類型的析構函數被調用,而不是該指針的類型的析構函數。

簡單講,這個類有虛函數就應該有虛析構函數。一旦你在類中加上了一個虛函數,你就已經需要為每一個對象支付空間代價(每個對象一個指針),所以這時使析構函數成為虛擬的通常不會額外付出什么。

對于那些trivial且沒有子類的類,虛析構函數只會增加開銷,不要使用

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/458370.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/458370.shtml
英文地址,請注明出處:http://en.pswp.cn/news/458370.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

(八)企業部分之nginx+tomcat+memcached負載均衡集群搭建

【server1】vim /usr/local/lnmp/tomcat/conf/context.xml<Context>......<Manager className"de.javakaffee.web.msm.MemcachedBackupSessionManager"memcachedNodes"n1:172.25.45.1:11211,n2:172.25.45.2:11211"failoverNodes"n1"req…

泛型算法(二十三)之排列算法

1、is_permutation(ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2)&#xff1a;C11版本&#xff0c;判斷兩個序列是否為同一元素集的兩個排列。 std::vector<int> c1 {1, 2, 3, };std::vector<int> c2 {1, 2, 3, 1, 3};//判斷兩個序…

C++ 虛函數經典深入解析

from&#xff1a;https://blog.csdn.net/gggg_ggg/article/details/45915505C中的虛函數的作用主要是實現了多態的機制。 關于多態&#xff0c;簡而言之就是用父類型別的指針指向其子類的實例&#xff0c;然后通過父類的指針調用實際子類的成員函數。 這種技術可以讓父類的指針…

21OGNL與ValueStack(VS)-靜態方法訪問

轉自&#xff1a;https://wenku.baidu.com/view/84fa86ae360cba1aa911da02.html 在LoginAction中增加如下方法&#xff1a;public static String getSta() { return "這是LoginAction中的靜態方法"; } 然后在loginSuc.jsp中增加如下代碼&#xff1a; 調用Action中的靜…

win7通過easyBCD引導ubuntu

我電腦配置了固態和傳統雙硬盤&#xff0c;SSD已經裝了win7&#xff0c;然后在傳統硬盤上安裝ubuntu&#xff0c;結果安裝完成后看不到ubuntu的入口。因為跟win7不是裝在一個驅動設備上&#xff0c;所以使用easyBCD的Linux&#xff0f;BCD選項也無法正確引導。最后通過easyBCD的…

深入理解C++中的explicit關鍵字

深入理解C中的explicit關鍵字kezunhaigmail.com http://blog.csdn.net/kezunhaiC中的explicit關鍵字只能用于修飾只有一個參數的構造函數, 它的作用是表明該構造函數是顯示的, 而非隱式的&#xff0c; 跟它相對應的另一個關鍵字是implicit, 意思是隱藏的,構造函數默認情況下即聲…

JAVA面試中問及HIBERNATE與 MYBATIS的對比,在這里做一下總結(轉)

hibernate以及mybatis都有過學習&#xff0c;在java面試中也被提及問道過&#xff0c;在項目實踐中也應用過&#xff0c;現在對hibernate和mybatis做一下對比&#xff0c;便于大家更好的理解和學習&#xff0c;使自己在做項目中更加得心應手。 第一方面&#xff1a;開發速度的對…

Caffe源碼解析4: Data_layer

轉載請注明出處&#xff0c;樓燚(y)航的blog&#xff0c;http://home.cnblogs.com/louyihang-loves-baiyan/ data_layer應該是網絡的最底層&#xff0c;主要是將數據送給blob進入到net中&#xff0c;在data_layer中存在多個跟data_layer相關的類 BaseDataLayerBasePrefetchingD…

理解C++中拷貝構造函數

拷貝構造函數的功能是用一個已有的對象來初始化一個被創建的同樣對象&#xff0c;是一種特殊的構造函數&#xff0c;具有一般構造函數的所有特性&#xff0c;當創建一個新對象的時候系統會自動調用它&#xff1b;其形參是本類對象的引用&#xff0c;它的特殊功能是將參數代表的…

IDEA mybatis-generator-maven-plugin 插件的使用

2019獨角獸企業重金招聘Python工程師標準>>> pom.xml中添加插件 <plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuratio…

python優秀網友學習筆記推薦

AstralWindMr.Seven 轉載于:https://www.cnblogs.com/migongci0412/p/5154892.html

深入理解CRITICAL_SECTION

摘要臨界區是一種防止多個線程同時執行一個特定代碼節的機制&#xff0c;這一主題并沒有引起太多關注&#xff0c;因而人們未能對其深刻理解。在需要跟蹤代碼中的多線程處理的性能時&#xff0c;對 Windows 中臨界區的深刻理解非常有用。本文深入研究臨界區的原理&#xff0c;以…

webpack進階之插件篇

上一篇博客講解了webpack環境的基本&#xff0c;這一篇講解一些更深入的內容和開發技巧。基本環境搭建就不展開講了 一、插件篇 1. 自動補全css3前綴 autoprefixer 官方是這樣說的&#xff1a;Parse CSS and add vendor prefixes to CSS rules using values from the Can I Use…

QT:QObject 簡單介紹

QObject 是所有Qt對象的基類。QObject 是Qt模塊的核心。它的最主要特征是關于對象間無縫通信的機制&#xff1a;信號與槽。 使用connect()建立信號到槽的連接&#xff0c;使用disconnect()銷毀連接&#xff0c;使用blockSignals()暫時阻塞信號以避免無限通知循環&#xff0c;使…

利用malloc定義數組

使用malloc方法時&#xff0c;應導入文件 #include<malloc.h> 1.利用malloc定義一維數組 int *num (int *)malloc(sizeof(int)*8); // 定義一個一維數組有8個元素&#xff0c;等價于 int num[8]; 2.利用malloc定義二維數組 int **num &#xff08; int **&#xff09…

C++中基類的析構函數為什么要用virtual虛析構函數

from&#xff1a;https://blog.csdn.net/iicy266/article/details/11906457知識背景要弄明白這個問題&#xff0c;首先要了解下C中的動態綁定。 關于動態綁定的講解&#xff0c;請參閱&#xff1a; C中的動態類型與動態綁定、虛函數、多態實現 正題直接的講&#xff0c;C中基類…

第二章 Python基本元素:數字、字符串和變量

Python有哪些內置的數據類型&#xff1a; True False #布爾型 42 100000000 #整型 3.14159 1.0e8 #浮點型 abcdes #字符串 2.1 變量、名字和對象 python中統一的形式是什么&#xff1f; 對象&#xff0c;所有的對象都是以對象的形式存在…

Mac - 設置NSButton 的背景色

- (void)drawRect:(NSRect)dirtyRect {[super drawRect:dirtyRect];[[NSColor clearColor] setFill];NSRectFill(self.bounds);self.wantsLayer YES;self.layer.cornerRadius 8;self.layer.masksToBounds YES; } 轉載于:https://www.cnblogs.com/741162830qq/p/5157046.html…

C++中static關鍵字作用總結

from&#xff1a;https://www.cnblogs.com/songdanzju/p/7422380.html1.先來介紹它的第一條也是最重要的一條&#xff1a;隱藏。&#xff08;static函數&#xff0c;static變量均可&#xff09; 當同時編譯多個文件時&#xff0c;所有未加static前綴的全局變量和函數都具有全局…

C Primer Plus 第7章 C控制語句:分支和跳轉 7.4 一個統計字數的程序

2019獨角獸企業重金招聘Python工程師標準>>> 首先&#xff0c;這個程序應該逐個讀取字符&#xff0c;并且應該有些方法判斷何時停止&#xff1b;第二&#xff0c;它應該能夠識別并統計下列單位&#xff1a;字符、行和單詞。下面是偽代碼描述&#xff1a; read a cha…