日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

让C++对象只能分配到堆/栈和静态区上并判断分配位置

發布時間:2024/4/18 c/c++ 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 让C++对象只能分配到堆/栈和静态区上并判断分配位置 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

對象分配到堆上的過程:三個形式的new

要把對象分配到棧上,需要使用到new operator,而new operator會調用operator newplacement new

  • operator new用于調用malloc申請堆空間,如果申請失敗會拋出bad_alloc異常
  • placement new用于“定向構造”,即在指定的內存上(這里就是指operator new申請的空間)調用構造函數以構造出新對象

根據侯捷先生的書所說,STL(書中的版本)對于某些對象做了統一的分配和統一的構造,而不把這兩個步驟通過直接調用new operator合二為一。

實際上,placement new也有其他作用,例如可以將一個對象構造在已知的棧空間,或者全局靜態區空間上。(當然那樣就不能對那個對象調用delete啦,會core dump的),代碼就像這樣:

char buf[100]{};new (buf) HeapOnly();HeapOnly *h = static_cast<HeapOnly *>((void*)buf);delete h; //!!! crash

可見C++給了程序員足夠多的自由。

如何限制只能在堆上

一個很自然的想法是,既然只能在堆上,那么分配的方法就只有new了,那把構造函數private掉就可以了。事實上這是不對的,因為如上文所講,new operator會隱式調用placement new,placement new會隱式調用構造函數,造成了在類外訪問構造函數,所以不能把構造函數設置為private。如果這么干,只要在類外,堆和棧上都分配不了。
解決方案如下:

class HeapOnly{public:HeapOnly() = default;void Destroy(){delete this;} // 必須要提供private: // none protected!~HeapOnly() = default;};

那么為什么要把析構函數聲明private呢?這里是兩個問題,依次解決。

  • 為什么要在析構函數上動手腳?根據前文,把構造函數private掉是不行了,但是如果對析構函數這么做,就會形成能在棧上分配,但是卻無法在函數結束時釋放的情況,編譯器會在編譯期發現這種錯誤,報出編譯錯誤。
    但是請注意,這樣做的話,就無法顯式的delete掉HeapOnly及其派生類了。和new operator類似,delete operator也會先調用對象的析構函數再釋放內存,這樣又造成了在類外訪問private成員的情況,所以必須要提供一個銷毀該對象的接口Destroy()。

  • 為什么是聲明為private而不是protected呢?眾所周知,protected的意思是子類可見,private是只有自己可見。寫這樣一個HeapOnly的類的意義類似于boost::noncopyable,要讓所有繼承該類的子類都只能被分配到堆上。

    • HeapOnly的析構函數protected時, 假設派生類叫Derive,HeapOnly的析構函數對于Derive是可見的,Derive如果沒有再把析構函數對外隱藏,那么在堆上析構Derive就有了可能:因為Derive的析構函數會轉而調用基類的析構函數,Derive也就可以在棧上分配了!所以聲明為protected是不行的。
    • HeapOnly的析構函數private時, 假設派生類叫Derive,HeapOnly的析構函數對于Derive是不可見的,如果要析構棧上對象Derive,就必須在Derive的析構函數中保證基類析構函數的可見性,HeapOnly的析構函數是private時,Derive的析構函數是無法調用基類析構函數的(因為private對子類不可見)。編譯器會報這個這樣的錯誤:
    Call to implicitly-deleted default constructor of 'Derive'

    可見,編譯器認為既然Derive無法調用基類的析構函數,那就認為Derive的析構函數也是隱式的被刪除了吧。所以Derive就無法在棧上分配了。

  • 題外話:什么時候析構函數應該是virtual的?就算析構函數不是virtual的,顯式的刪除一個棧上的派生對象,也會根據繼承層次一路從派生類析構到基類。但是如果是根據一個基類指針刪除堆上的派生對象,如果析構函數不是virtual的,那么派生類的析構函數就不會被調用……一句話總結,就是:這樣做是為了當用一個基類的指針刪除一個派生類的對象時,派生類的析構函數會被調用。

  • 如何限制只能在棧上(當然這里也可以是靜態區)

    這個就比只能在堆上分配要簡單一些,只需要把類中operator new[]和operator new[]聲明為protected就可以了。

    class NoHeap {protected:static void *operator new(std::size_t);static void *operator new [] (std::size_t);};

    需要注意的有兩點:

  • 這兩個函數都是static的,因為它們的生命周期比對象長(畢竟靠他們創建對象)
  • 前文提到過,operator new是用來分配空間的,所以子類不僅需要調用自己的operator new,也需要調用基類的operator new,所以把operator new設置為protected就足以對外隱藏NoHeap的所有子類的operator new了,導致所有的子類都無法在堆上分配。
  • 如何運行時判斷在堆還是在棧上

    首先需要明確的是,前面所講的都是在編譯期對于程序員使用一個類型的方法的約束#,當然在運行期是沒辦法做這個約束了(畢竟程序都跑起來了),不過有沒有辦法判斷一個對象到底是在堆上還是在棧上呢?可以試試下面這個類:

    class Detect {public:Detect(){int i;Check(&i);}void Check(int *i) {int j;if ((i < &j) == ((void*)this < (void*)&j)) {std::cout << "Stack" << std::endl;return;}else{std::cout << "Heap" << std::endl;return;}}};

    在Detect的構造函數中分配一個局部變量i,再在調用的Check函數中分配局部變量j,如果Detect在棧上分配,那么典型的情況應該像這樣:

    this ----> Detect() ---> int i ---> Check() ---> int j高地址------------------------------------------>低地址

    當然堆棧也有可能是從低地址向高地址生長的,視機器而定。Check里通過判斷&i/&j/this三者的相對位置,使得這兩種情況都可以應對。當然并不保證所有機器都適用,在我的x86_64電腦上測試通過。

    總結

    以上是生活随笔為你收集整理的让C++对象只能分配到堆/栈和静态区上并判断分配位置的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。