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

一:首先複習下內存結構;
1:棧(stack):–由編譯器自動分配釋放,存放函數參數值,局部變量值等.
2:堆(heap):一般是由程序員分配釋放,若程序不釋放,程序結束時可能由操作系統回收
3:全局區(靜態區):主要存放全局變量和靜態變量,分爲已初始化的全局變量和靜態變量區,data區,未初始化的全局變量和靜態變量區(bbs區);
4:常量區:各種常量字符
5:代碼段:存放函數二進制代碼(code區);

二:C++類的創建的方式
c++中,類的建立分爲兩種,一種是靜態建立,如A a,另一種是動態建立,如A*ptr = new A;這兩種方法是有區別的
1:靜態建立類對象:是由編譯器爲對象在棧空間分配內存,是通過直接移動棧頂指針,挪出適當的空間,然後在這片內存空間上調用構造函數形成一個棧對象,這種方法直接調用類的構造函數

2:動態建立類的對象,是使用new運算符將對象建立在堆空間中,這個過程分爲兩步,第一步是執行operator new[]函數,在堆空間中搜索合適的內存並進行分配;第二步是調用構造函數構造對象,初始化這片內存空間,這種方法,間接調用類的構造函數.
三:定義一個只能在堆上分配的對象
就是不能靜態建立對象,即不能直接調用類的的構造函數

這時我們很快想到將類的對象定義成私有,這樣就無法調用構造函數來構造類的對象,只能通過new運算符來建立對象,然而,前面我們瞭解到new運算符執行有兩步,C++提供new運算符的重載,其實是隻允許重載operator new()函數,而operator new()函數只用於分配內存,無法提供構造功能,因此這種方法不可以.

這時我們考慮將對象建立在棧上面,是由編譯器分配內存空間的,調用構造函數來構造棧對象.當對象使用完後,編譯器會調用析構函數來釋放棧對象所佔的空間,編譯器管理了對象的整個生命週期.如果編譯器無法調用類的析構函數,比如,類的析構函數是私有的,編譯器無法調用析構函數來釋放內存,所以,編譯器爲類對象分配棧空間時會先考慮析構函數訪問的 可能性,其實不光是析構函數,只要是非靜態的函數,編譯器都會檢查.如果類的析構函數是私有的,則編譯器不會在棧空間上分配內存,因此,將析構函數設爲私有,類對象就無法建立在棧上了;
方法一:

#include<iostream>
using namespace std;
class A
{
public:
    A()
    {}
    void Clean()
    {
        delete this;
    }
private:
 ~A()
    {}
};
int main()
{
    A a;
    return 0;
}

這裏寫圖片描述
我們發現編譯會報錯,提示析構函數無法訪問,這樣只能使用new 操作符來建立對象,構造函數是公有的,可以直接調用. 類中必須提供一個destory函數,來進行內存空間的釋放。類對象使用完成後,必須調用destory函數。

缺點是:
1:無法解決繼承問題
2:類的使用不符合規則,使用new 創建的對象,調用自定義函數Clean來釋放對象,而不是使用delete(使用delete會報錯,因爲delete對象的指針,會調用對象的析構函數,而析構函數不可訪問.)如何解決,可以將構造函數設爲保護,然後提供一個公有的static函數來完成構造,這樣不使用new,而是使用一個函數來構造,使用一個函數來析構.
方法二:

class A
{
protected:
   A()
    {}
public:
  static A*Create()
    {
        return new A();
    }
    void Clean()
    {
        delete this;
    }
};
int main()
{

    A* ht4 =  A::Create();
    A ht5(*ht4);
    return 0;
}

四:定義一個只能在棧上生成的對象
只有使用new運算符,對象纔會建立在堆上,因此只要禁用new 運算符就可以實現類對象只能建立在棧上.雖然不能影響new operator的能力,但是new operator總是先調用operator new,而後面我們是可以自行聲明重寫的.因此,將operator new()設爲私有即可禁止對象被new在堆上.

class B
{
  void *operator new(size_t );
void operator delete(void *ptr);
public:
  B(){}
~B(){}
};
int main()
{
   B b;
    return 0;
}

五:棧和堆的區別
1:申請方式:棧是系統自動申請和 釋放,堆則由程序員顯示申請
2:申請和釋放堆內存的API在C語言中是malloc和free,在C++中是new/delete.申請與釋放一定要配對使用,否則就會出現內存泄露,時間長了就會出現OOM(out of memmory)錯誤.

一般在return /exit或者break/continue這些語句容易忘記釋放內存,所以要檢查內存泄露時要關注這些語句,前他們是否有必要的釋放語句free/delete

3:所佔空間:堆的空間比較大,棧比較小,所以申請大的內存一般在堆中申請,棧不要用太大的內存結構,比如大的靜態數組,否則不容易出現棧溢出的情況

4:關於生命週期:棧較短(隨着函數退出或者返回)也就是棧幀的結束而終止.本函數的棧完成了使用,堆要在什麼時候釋放,生命週期就在什麼時候結束
5:棧是向下生長,堆是向上生長的.

部分參考:http://blog.csdn.net/hxz_qlh/article/details/13135433

發佈了113 篇原創文章 · 獲贊 62 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章