如何定義一個只能在(堆/棧)上生成對象的類

 

前幾天C語言與C++面試知識總結這篇文章中,有總結定義一個只能在堆上(棧上)生成對象的類以及方法和原因,在裏說一下具體實現

在C++中,類的對象建立分爲兩種:

  1. 一種是靜態建立,如A a;

  • 靜態建立一個類對象,是由編譯器爲對象在棧空間中分配內存,是通過直接移動棧頂指針,挪出適當的空間,然後在這片內存空間上調用構造函數形成一個棧對象。使用這種方法,直接調用類的構造函數。

  1. 一種是動態建立,如A* ptr=new A;這兩種方式是有區別的。

  • 動態建立類對象,是使用new運算符將對象建立在堆空間中。這個過程分爲兩步,第一步是執行operator new()函數,在堆空間中搜索合適的內存並進行分配;第二步是調用構造函數構造對象,初始化這片內存空間。這種方法,間接調用類的構造函數。

1 只能在堆上

方法:將析構函數設置爲私有

原因:C++ 是靜態綁定語言,編譯器管理棧上對象的生命週期,編譯器在爲類對象分配棧空間時,會先檢查類的析構函數的訪問性。若析構函數不可訪問,則不能在棧上創建對象。

思路:容易想到將構造函數設爲私有。在構造函數私有之後,無法在類外部調用構造函數來構造類對象,只能使用new運算符來建立對象。然而,前面已經說過,new運算符的執行過程分爲兩步,C++提供new運算符的重載,其實是隻允許重載operator new()函數,而operator()函數用於分配內存,無法提供構造功能。因此,這種方法不可以。

當對象建立在棧上面時,是由編譯器分配內存空間的,調用構造函數來構造棧對象。當對象使用完後,編譯器會調用析構函數來釋放棧對象所佔的空間。編譯器管理了對象的整個生命週期。如果編譯器無法調用類的析構函數,情況會是怎樣的呢?比如,類的析構函數是私有的,編譯器無法調用析構函數來釋放內存。所以,編譯器在爲類對象分配棧空間時,會先檢查類的析構函數的訪問性,其實不光是析構函數,只要是非靜態的函數,編譯器都會進行檢查。如果類的析構函數是私有的,則編譯器不會在棧空間上爲類對象分配內存。

將析構函數設爲私有,類對象就無法建立在棧上。

代碼

#include<iostream>
using namespace std;

//只在棧上開闢 2020.05.22
class A
{
public:
    A(){}
    void destory()
	{
		delete this;
	}
private:
    ~A(){}
};

int main()
{

   A a;

  return 0;
}

使用A a建立對象,編譯報錯

提示析構函數無法訪問。這樣就只能使用new操作符來建立對象,構造函數是公有的,可以直接調用。類中必須提供一個destory函數,來進行內存空間的釋放。類對象使用完成後,必須調用destory函數。

上述方法的一個缺點就是,無法解決繼承問題。如果A作爲其它類的基類,則析構函數通常要設爲virtual,然後在子類重寫,以實現多態。因此析構函數不能設爲private。還好C++提供了第三種訪問控制,protected。將析構函數設爲protected可以有效解決這個問題,類外無法訪問protected成員,子類則可以訪問。

另一個問題是,類的使用很不方便,使用new建立對象,卻使用destory函數釋放對象,而不是使用delete。(使用delete會報錯,因爲delete對象的指針,會調用對象的析構函數,而析構函數類外不可訪問)這種使用方式比較怪異。爲了統一,可以將構造函數設爲protected,然後提供一個public的static函數來完成構造,這樣不使用new,而是使用一個函數來構造,使用一個函數來析構。代碼如下,類似於單例模式:

#include<iostream>
using namespace std;

//只在棧上開闢 2020.05.22
class A
{
protected:
	A(){}
	~A(){}
public:
	static A* create()
	{
		return new A();
	}
	void destory()
	{
		delete this;
	}
};


int main()
{
	A* ptr = A::create();
	ptr->destory();
	return 0;
}

這樣,調用create()函數在堆上創建類A對象,調用destory()函數釋放內存。

2 只能在棧上

方法:將 new 和 delete 重載爲私有

原因:在堆上生成對象,使用 new 關鍵詞操作,其過程分爲兩階段:第一階段,使用 new 在堆上尋找可用內存,分配給對象;第二階段,調用構造函數生成對象。將 new 操作設置爲私有,那麼第一階段就無法完成,就不能夠在堆上生成對象。

思路:只有使用new運算符,對象纔會建立在堆上,因此,只要禁用new運算符就可以實現類對象只能建立在棧上。將operator new()設爲私有即可。

代碼

class  A
{
private :
    void * operator  new ( size_t  t){}      // 注意函數的第一個參數和返回值都是固定的
    void  operator  delete ( void * ptr){}  // 重載了new就需要重載delete
public :
    A(){}
    ~A(){}
};

參考資料

https://www.nowcoder.com/questionTerminal/0a584aa13f804f3ea72b442a065a7618

最新原創推薦

十大經典排序算法(動態演示+代碼)

C語言與C++面試知識總結

數據結構之堆棧

一文輕鬆理解內存對齊

一文輕鬆理解打印有效日誌

一文讀懂C語言與C++動態內存

面試中常見的C語言與C++區別的問題

數據結構之線性表

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章