[cpp]C++中的解構函式

芒果和小貓發表於2020-06-19

C++中的解構函式

簡介

解構函式(Destructors),是物件的成員函式,沒有返回值也沒有引數,且一個類只有一個解構函式,當物件被銷燬的時候呼叫,被銷燬通常有這麼幾個情況。

  • 函式執行結束
  • 程式執行結束
  • 程式塊包含的區域性變數
  • delete操作

什麼時候要自己寫解構函式?

編譯器會自動建立預設的解構函式,通常都沒有問題,但是當我們在類中動態分配了記憶體空間時,我們需要手段的回收這塊空間,防止記憶體溢位。就像這樣

class String 
{ 
private: 
	char *s; 
	int size; 
public: 
	String(char *); // constructor 
	~String();	 // destructor 
}; 
 
String::String(char *c) 
{ 
	size = strlen(c); 
	s = new char[size+1]; 
	strcpy(s,c); 
} 
 
String::~String() 
{ 
	delete []s; 
} 

私有的解構函式

可以將解構函式的訪問許可權設定為private,設定時沒有問題的,但是一個問題就是,通常的手段就沒法呼叫解構函式了。

如下所示,程式結束後要呼叫解構函式,但是解構函式時私有的沒法呼叫,所以會編譯出錯。

#include <iostream> 
using namespace std; 
class Test { 
private: 
	~Test() {} 
}; 
int main() 
{ 
	Test t; 
} 

以下這樣不會有問題,因為沒有物件被建立,也不用析構

int main() 
{ 
    Test* t;                                          
} 

以下這樣也不會有問題,因為動態分配的記憶體需要程式設計師手段釋放,所以程式結束時沒有釋放記憶體,也沒有呼叫解構函式。這裡插一句,動態分配的記憶體如果不手動釋放,程式結束後也會不會釋放,但是現代作業系統可以幫我們釋放,因為這個動態分配的記憶體和這個程式有關,作業系統應該可以捕獲到這個洩露的記憶體從而釋放。(查資料看到的)

int main() 
{ 
    Test* t = new Test; 
} 

如果使用delete來刪除物件,會編譯出錯

int main() 
{ 
    Test* t = new Test;
    delete t;//編譯出錯,無法呼叫私有的解構函式 
}

可以利用Friend函式,進行物件的銷燬,因為Friend可以訪問私有成員,所以可以訪問解構函式。


#include <iostream> 

class Test { 
private: 
	~Test() {} 
	friend void destructTest(Test*); 
}; 

void destructTest(Test* ptr) 
{ 
	delete ptr; 
} 

int main() 
{ 
	Test* ptr = new Test; 
	destructTest(ptr); 

	return 0; 
} 

或者給類寫一個銷燬的方法,在需要銷燬的時候呼叫。

class Test { 
public:
    destroy(){delete this};
private: 
	~Test() {} 
};

那麼什麼時候需要使用私有的解構函式呢?當我們只希望動態分配物件空間(在堆上)時候,用私有析構,就防止了在棧上分配,因為在編譯階段就會出錯。

虛解構函式

當類用到多型的特性時候,使用虛解構函式。看如下的例子。

#include <iostream>
using namespace std;
class Base
{
public:
    Base(){
        cout << "Base Constructor Called\n";
    }
    ~Base(){
        cout << "Base Destructor called\n";
    }
};
class Derived1: public Base
{
public:
    Derived1(){
        cout << "Derived constructor called\n";
    }
    ~Derived1(){
        cout << "Derived destructor called\n";
    }
};
int main()
{
    Base *b = new Derived1();
    delete b;
}

例子裡的解構函式都不是虛擬函式,當我們想用基類的指標來刪除派生類物件的時候,就出現了問題,“undefined behavior”,c++標準裡規定,只由編譯器實現,通常這時不會報錯,會呼叫基類的解構函式。但這應該不是我們想要的,這會導致記憶體洩漏。所以要把解構函式置為虛擬函式。(msvc似乎不用給解構函式加virtual,預設就是虛的,gcc沒有預設還是要加的)

另外虛解構函式可以是純虛解構函式,但是要提供函式體,不然沒法析構,因為虛解構函式和一般的虛擬函式的overide還不一樣,虛解構函式要挨個執行,不提供函式體,會編譯出錯。

解構函式執行的順序

派生類,成員物件,基類這樣

class B
{public: virtual ~B(){cout<<"基類B執行了"<<endl; }
};

class D
{public:virtual ~D(){cout<<"成員D執行了"<<endl; }
} ;

class E
{public:virtual ~E(){cout<<"成員E執行了"<<endl; }
} ;

class A
{public:virtual ~A(){cout<<"基類A執行了"<<endl;}; 
};

class C:public A,B
{
    public:virtual ~C(){cout<<"派生類執行了"<<endl;};
    private:
        E e;
        D d;
};

int main()  
{  
    C *c;
    c=new C();
    delete c;
}   

結果為:

  • 派生類執行了
  • 成員D執行了
  • 成員E執行了
  • 基類B執行了
  • 基類A執行了

參考

  • [1]什麼時候使用虛擬函式https://stackoverflow.com/questions/461203/when-to-use-virtual-destructors
  • [2]解構函式https://www.geeksforgeeks.org/destructors-c/
  • [3]虛解構函式https://www.geeksforgeeks.org/virtual-destructor/
  • [4]純解構函式https://www.geeksforgeeks.org/pure-virtual-destructor-c/

相關文章