構造函數和析構函數是兩個很是特殊的函數:它們沒有返回值。這與返回值爲void的函數顯然不一樣,後者雖然也不返回任何值,但還可讓它作點別的事情,而構造函數和析構函數則不容許。ios
在程序中建立和消除一個對象的行爲很是特殊,就像出生和死亡,並且老是由編譯器來調用這些函數以確保它們被執行。若是它們有返回值,要麼編譯器必須知道如何處理返回值,要麼就只能由客戶程序員本身來顯式的調用構造函數與析構函數,這樣一來,安全性就被破壞了。另外,析構函數不帶任何參數,由於析構不需任何選項。程序員
若是容許構造函數有返回值,在某此狀況下,會引發歧義。以下兩個例子
數組
class C { public: C(): x(0) { } C(int i): x(i) { } private: int x; };
若是C的構造函數能夠有返回值,好比:安全
int:int C():x(0) { return 1; } //1表示構形成功,0表示失敗
那麼下列代碼會發生什麼事呢?
數據結構
C c = C(); //此時c.x == 1
分析:ide
很明顯,C()調用了C的無參數構造函數,該構造函數返回int值1。函數
按照C++的規定,C c = C();是用默認構造函數建立一個臨時對象,並用這個臨時對象初始化c,此時,c.x的值應該是0。spa
可是,若是C::C()有返回值,而且返回了1(爲了表示成功),則C++會用1去初始化c,即調用帶參數構造函數C::C(int i)。獲得的c.x便會是1。因而,語義產生了歧義。設計
構造函數的調用之因此不設返回值,是由於構造函數的特殊性決定的。從基本語義角度來說,構造函數返回的應當是所構造的對象。不然,咱們將沒法使用臨時對象:指針
void f(int a) {...} //(1) void f(const C& a) {...} //(2) f(C()); //(3),究竟調用誰?
對於(3),咱們但願調用的是(2),但若是C::C()有int類型的返回值,那麼到底是調用(1)好呢,仍是調用(2)好呢?因而,咱們的重載體系,乃至整個的語法體系都會崩潰。
這裏的核心是表達式的類型:目前表達式C()的類型是類C,但若是C::C()有返回類型R,那麼表達式C()的類型應當是R,而不是C,因而便會引起上述的類型問題。
#include <iostream> using namespace std; class MyClass { public: MyClass() { cout << "Constructors" << endl; } ~MyClass() { cout << "Destructors" << endl; } }; int main() { MyClass* pMyClass = new MyClass; pMyClass->~MyClass(); delete pMyClass; return 0; }
結果:
Constructors Destructors //這個是顯示調用的析構函數 Destructors //這個是delete調用的析構函數
這有什麼用?有時候,在對象的生命週期結束前,想先結束這個對象的時候就會派上用場了(只是終結對象,其內存空間未被釋放)。直接調用析構函數並不釋放對象所在的內存。
new和delete分析:
new的時候,其實作了三件事:
(1)調用::operator new分配所需內存;
(2)調用構造函數;
(3)返回指向新分配並構造的對象的指針。
delete的時候,作了兩件事:
(1)調用析構函數;
(2)調用::operator delete釋放內存。
因此推測構造函數也是能夠顯式調用的。作個實驗:
int main() { MyClass* pMyClass = (MyClass*)malloc(sizeof(MyClass)); pMyClass->MyClass(); // … }
編譯pMyClass->MyClass()出錯:
error C2273: 'function-style cast' : illegal as right side of '->'operator
它覺得MyClass是這個類型。
解決辦法有兩個:
(1)pMyClass->MyClass::MyClass();
(2)new(pMyClass) MyClass();
顯示調用構造函數有什麼用?
有時候,你可能因爲效率考慮要用到malloc去給類對象分配內存,由於malloc是不調用構造函數的,因此這個時候會派上用場了。
另外下面也是能夠的,雖然內置類型沒有構造函數。
int* i = (int*)malloc(sizeof(int)); new (i) int();
當你嘗試着把拷貝構造函數寫成值傳遞的時候,會發現編譯都通不過,錯誤信息以下:
error: invalid constructor; you probably meant 'S (const S&)' //大體意思是:無效的構造函數,你應該寫成。。。
當編譯錯誤的時候你就開始糾結了,爲何拷貝構造函數必定要使用引用傳遞呢,我上網查找了許多資料,你們的意思基本上都是說若是用值傳遞的話可能會產生死循環。編譯器可能基於這樣的緣由不容許出現值傳遞的拷貝構造函數,也有多是C++標準是這樣規定的。
若是真是產生死循環這個緣由的話,應該是這樣子的:
class S { public: S(int x):a(x){ } S(const S st) //拷貝構造函數 { a = st.a; } private: int a; }; int main() { S s1(2); S s2(s1); return 0; }
當給s2初始化的時候調用了s2的拷貝構造函數,因爲是值傳遞,系統會給形參st從新申請一段空間,而後調用自身的拷貝構造函數把s1的數據成員的值傳給st。當調用自身的拷貝構造函數的時候又由於是值傳遞,因此...
也就是說,只要調用拷貝構造函數,就會從新申請一段空間,只要從新申請一段空間,就會調用拷貝構造函數,這樣一直下去就造成了一個死循環。因此拷貝構造函數必定不能是值傳遞。
構造函數拋出異常:
一、不建議在構造函數中拋出異常;
二、構造函數拋出異常時,析構函數將不會被執行;
C++僅僅能刪除被徹底構造的對象(fully contructed objects),只有一個對象的構造函數徹底運行完畢,這個對象才能被徹底地構造。對象中的每一個數據成員應該清理本身,若是構造函數拋出異常,對象的析構函數將不會運行。若是你的對象須要撤銷一些已經作了的動做(如分配了內存,打開了一個文件,或者鎖定了某個信號量),這些須要被撤銷的動做必須被對象內部的一個數據成員記住處理。
析構函數拋出異常:
在有兩種狀況下會調用析構函數。
(1)在正常狀況下刪除一個對象,例如對象超出了做用域或被顯式地delete;
(2)異常傳遞的堆棧展轉開解(stack-unwinding)過程當中,由異常處理系統刪除一個對象。
在上述兩種狀況下,調用析構函數時異常可能處於激活狀態也可能沒有處於激活狀態。遺憾的是沒有辦法在析構函數內部區分出這兩種狀況。所以在寫析構函數時你必須保守地假設有異常被激活,由於若是在一個異常被激活的同時,析構函數也拋出異常,並致使程序控制權轉移到析構函數外,C++將調用terminate函數。這個函數的做用正如其名字所表示的:它終止你程序的運行,並且是當即終止,甚至連局部對象都沒有被釋放。
歸納以下:
一、析構函數不該該拋出異常;
二、當析構函數中會有一些可能發生異常時,那麼就必需要把這種可能發生的異常徹底封裝在析構函數內部,決不能讓它拋出函數以外;
三、當處理另外一個異常過程當中,不要從析構函數拋出異常;
在構造函數和析構函數中防止資源泄漏的好方法就是使用smart point(智能指針),C++ STL提供了類模板auto_ptr,用auto_ptr對象代替原始指針,你將再也不爲堆對象不能被刪除而擔憂,即便在拋出異常時,對象也能被及時刪除。由於auto_ptr的析構函數使用的是單對象形式的delete,而不是delete [],因此auto_ptr不能用於指向對象數組的指針。當複製 auto_ptr 對象或者將它的值賦給其餘 auto_ptr 對象的時候,將基礎對象的全部權從原來的 auto_ptr 對象轉給副本,原來的 auto_ptr 對象重置爲未綁定狀態。所以,不能將 auto_ptrs 存儲在標準庫容器類型中。若是要將智能指針做爲STL容器的元素,可使用Boost庫裏的shared_ptr。
簡單解釋:
構造不重載,怎麼實現多樣化?
析構沒有參數,怎麼重載?
總結:構造函數可重載,析構函數沒法重載。
構造函數的調用順序老是以下:
一、基類構造函數。
若是有多個基類,則構造函數的調用順序是某類在類派生表中出現的順序,而不是它們在成員初始化表中的順序。
二、成員類對象構造函數。
若是有多個成員類對象則構造函數的調用順序是對象在類中被聲明的順序,而不是它們出如今成員初始化表中的順序。
三、派生類構造函數。
析構函數的調用順序與構造函數的調用順序正好相反:首先調用派生類的析構函數;其次再調用成員類對象的析構函數;最後調用基類的析構函數。
析構函數在下邊3種狀況時被調用:
一、對象生命週期結束,被銷燬時(通常類成員的指針變量與引用都i不自動調用析構函數);
二、delete指向對象的指針時,或delete指向對象的基類類型指針,而其基類虛構函數是虛函數時;
三、對象i是對象o的成員,o的析構函數被調用時,對象i的析構函數也被調用。
下面用例子來講說構造函數的的調用順序:
#include "stdafx.h" #include "iostream" using namespace std; class Base { public: Base(){ std::cout<<"Base::Base()"<<std::endl; } ~Base(){ std::cout<<"Base::~Base()"<<std::endl; } }; class Base1:public Base { public: Base1(){ std::cout<<"Base1::Base1()"<<std::endl; } ~Base1(){ std::cout<<"Base1::~Base1()"<<std::endl; } }; class Derive { public: Derive(){ std::cout<<"Derive::Derive()"<<std::endl; } ~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; } }; class Derive1:public Base1 { private: Derive m_derive; public: Derive1(){ std::cout<<"Derive1::Derive1()"<<std::endl; } ~Derive1(){ std::cout<<"Derive1::~Derive1()"<<std::endl; } }; int _tmain(int argc, _TCHAR* argv[]) { Derive1 derive; return 0; }
運行結果是:
Base::Base() Base1::Base1() Derive::Derive() Derive1::Derive1() Derive1::~Derive1() Derive::~Derive() Base1::~Base1() Base::~Base()
那麼根據上面的輸出結果,進行一下講解,構造函數的調用順序是;首先,若是存在基類,那麼先調用基類的構造函數,若是基類的構造函數中仍然存在基類,那麼程序會繼續進行向上查找,直到找到它最先的基類進行初始化;如上例中類Derive1,繼承於類Base與Base1;其次,若是所調用的類中定義的時候存在着對象被聲明,那麼在基類的構造函數調用完成之後,再調用對象的構造函數,如上例中在類Derive1中聲明的對象Derive m_derive;最後,將調用派生類的構造函數,如上例最後調用的是Derive1類的構造函數。
virtual析構函數
在C++中,構造函數不能聲時爲虛函數,這是由於編譯器在構造對象時,必須知道確切類型,才能正確的生成對象,所以,不容許使用動態束定;其次,在構造函數執行以前,對象並不存在,沒法使用指向此此對象的指針來調用構造函數,然而,析構函數是能夠聲明爲虛函數;C++明確指出,當derived class對象經由一個base class指針被刪除,而該base class帶着一個non-virtual析構函數,其結果未有定義---實際執行時一般發生的是對象的derived成分沒被銷燬掉。
看下面的例子:
class Base { public: Base(){ std::cout<<"Base::Base()"<<std::endl; } ~Base(){ std::cout<<"Base::~Base()"<<std::endl; } }; class Derive:public Base { public: Derive(){ std::cout<<"Derive::Derive()"<<std::endl; } ~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; } }; int _tmain(int argc, _TCHAR* argv[]) { Base* pBase = new Derive(); //這種base classed的設計目的是爲了用來"經過base class接口處理derived class對象" delete pBase; return 0; }
輸出的結果是:
Base::Base() Derive::Derive() Base::~Base()
從上面的輸出結果能夠看出,析構函數的調用結果是存在問題的,也就是說析構函數只做了局部銷燬工做,這可能造成資源泄漏敗壞數據結構等問題;那麼解決此問題的方法很簡單,給base class一個virtual析構函數;
class Base { public: Base(){ std::cout<<"Base::Base()"<<std::endl; } virtual ~Base(){ std::cout<<"Base::~Base()"<<std::endl; } }; class Derive:public Base { public: Derive(){ std::cout<<"Derive::Derive()"<<std::endl; } ~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; } }; int _tmain(int argc, _TCHAR* argv[]) { Base* pBase = new Derive(); delete pBase; return 0; }
輸出結果是:
Base::Base() Derive::Derive() Derive::~Derive() Base::~Base()
上面的輸出結果正是咱們所但願的。由此還能夠看出虛函數仍是多態的基礎,在C++中沒有虛函數就沒法實現多態特性,由於不聲明爲虛函數就不能實現「動態聯編」,因此也就不能實現多態啦!