構造函數與析構函數


1、構造函數和析構函數為什么沒有返回值?

構造函數和析構函數是兩個非常特殊的函數:它們沒有返回值。這與返回值為void的函數顯然不同,后者雖然也不返回任何值,但還可以讓它做點別的事情,而構造函數和析構函數則不允許。在程序中創建和消除一個對象的行為非常特殊,就像出生和死亡,而且總是由編譯器來調用這些函數以確保它們被執行。如果它們有返回值,要么編譯器必須知道如何處理返回值,要么就只能由客戶程序員自己來顯式的調用構造函數與析構函數,這樣一來,安全性就被人破壞了。另外,析構函數不帶任何參數,因為析構不需任何選項。

如果允許構造函數有返回值,在某此情況下,會引起歧義。如下兩個例子

復制代碼
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!!!
很明顯,C()調用了C的無參數構造函數。該構造函數返回int值1。恰好C有一個但參數構造函數C(int i)。於是,混亂來了。按照C++的規定,C c = C();是用默認構造函數創建一個臨時對象,並用這個臨時對象初始化c。此時,c.x的值應該是0。但是,如果C::C()有返回值,並且返回了1(為了表示成功),則C++會用1去初始化c,即調用但參數構造函數C::C(int i)。得到的c.x便會是1。於是,語義產生了歧義。使得C++原本已經非常復雜的語法,進一步混亂不堪。

構造函數的調用之所以不設返回值,是因為構造函數的特殊性決定的。從基本語義角度來講,構造函數返回的應當是所構造的對象。否則,我們將無法使用臨時對象:
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,於是便會引發上述的類型問題。

 

2、顯式調用構造函數和析構函數

復制代碼
#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的時候,其實做了三件事,一是:調用::operator new分配所需內存。二是:調用構造函數。三是:返回指向新分配並構造的對象的指針。
delete的時候,做了兩件事,一是:調用析構函數,二是:調用::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是這個類型。

解決辦法有兩個:
第一:pMyClass->MyClass::MyClass();
第二:new(pMyClass) MyClass();
第二種用法涉及C++ placement new 的用法。參考:http://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html

顯示調用構造函數有什么用?

有時候,你可能由於效率考慮要用到malloc去給類對象分配內存,因為malloc是不調用構造函數的,所以這個時候會派上用場了。
另外下面也是可以的,雖然內置類型沒有構造函數。
int* i = (int*)malloc(sizeof(int));
new (i) int();

3、拷貝(復制)構造函數為什么不能用值傳遞

當你嘗試着把拷貝構造函數寫成值傳遞的時候,會發現編譯都通不過,錯誤信息如下:
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。當調用自身的拷貝構造函數的時候又因為是值傳遞,所以...
也就是說,只要調用拷貝構造函數,就會重新申請一段空間,只要重新申請一段空間,就會調用拷貝構造函數,這樣一直下去就形成了一個死循環。所以拷貝構造函數一定不能是值傳遞。

4、構造函數/析構函數拋出異常的問題

構造函數拋出異常:
    1.不建議在構造函數中拋出異常;
    2.構造函數拋出異常時,析構函數將不會被執行;
C++僅僅能刪除被完全構造的對象(fully contructed objects),只有一個對象的構造函數完全運行完畢,這個對象才能被完全地構造。對象中的每個數據成員應該清理自己,如果構造函數拋出異常,對象的析構函數將不會運行。如果你的對象需要撤銷一些已經做了的動作(如分配了內存,打開了一個文件,或者鎖定了某個信號量),這些需要被撤銷的動作必須被對象內部的一個數據成員記住處理。

析構函數拋出異常:
    在有兩種情況下會調用析構函數。第一種是在正常情況下刪除一個對象,例如對象超出了作用域或被顯式地delete。第二種是異常傳遞的堆棧輾轉開解(stack-unwinding)過程中,由異常處理系統刪除一個對象。
在上述兩種情況下,調用析構函數時異常可能處於激活狀態也可能沒有處於激活狀態。遺憾的是沒有辦法在析構函數內部區分出這兩種情況。因此在寫析構函數時你必須保守地假設有異常被激活,因為如果在一個異常被激活的同時,析構函數也拋出異常,並導致程序控制權轉移到析構函數外,C++將調用terminate函數。這個函數的作用正如其名字所表示的:它終止你程序的運行,而且是立即終止,甚至連局部對象都沒有被釋放。
概括如下:
    1.析構函數不應該拋出異常;
    2.當析構函數中會有一些可能發生異常時,那么就必須要把這種可能發生的異常完全封裝在析構函數內部,決不能讓它拋出函數之外;
    3.當處理另一個異常過程中,不要從析構函數拋出異常;

    在構造函數和析構函數中防止資源泄漏的好方法就是使用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。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM