讓C 物件只能分配到堆 棧區的隨想

2022-07-31 21:12:19 字數 2725 閱讀 4739

要把物件分配到棧上,需要使用到new operator,而new operator會呼叫operator newplacement new

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

實際上,placement new也有其他作用,例如可以將乙個物件構造在已知的棧空間,或者全域性靜態區空間上。(當然那樣就不能對那個物件呼叫delete啦,會core dump的),**就像這樣:

char buf[100]{};

new (buf) heaponly();

heaponly *h = static_cast((void*)buf);

delete h; //!!! crash

可見c++給了程式設計師足夠多的自由。

乙個很自然的想法是,既然只能在堆上,那麼分配的方法就只有new了,那把建構函式private掉就可以了。事實上這是不對的,因為如上文所講,new operator會隱式呼叫placement new,placement new會隱式呼叫建構函式,造成了在類外訪問建構函式,所以不能把建構函式設定為private。如果這麼幹,只要在類外,堆和棧上都分配不了。

解決方案如下:

class heaponly

// 必須要提供

private: // none protected!

~heaponly() = default;

};

那麼為什麼要把析構函式宣告private呢?這裡是兩個問題,依次解決。

為什麼要在析構函式上動手腳?根據前文,把建構函式private掉是不行了,但是如果對析構函式這麼做,就會形成能在棧上分配,但是卻無法在函式結束時釋放的情況,編譯器會在編譯期發現這種錯誤,報出編譯錯誤。

但是請注意,這樣做的話,就無法顯式的delete掉heaponly及其派生類了。和new operator類似,delete operator也會先呼叫物件的析構函式再釋放記憶體,這樣又造成了在類外訪問private成員的情況,所以必須要提供乙個銷毀該物件的介面destroy()。

為什麼是宣告為private而不是protected呢?眾所周知,protected的意思是子類可見,private是只有自己可見。寫這樣乙個heaponly的類的意義類似於boost::noncopyable,要讓所有繼承該類的子類都只能被分配到堆上。

call to implicitly-deleted default constructor of 'derive'
可見,編譯器認為既然derive無法呼叫基類的析構函式,那就認為derive的析構函式也是隱式的被刪除了吧。所以derive就無法在棧上分配了。

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

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

class noheap 

;

需要注意的有兩點:

這兩個函式都是static的,因為它們的生命週期比物件長(畢竟靠他們建立物件)

前文提到過,operator new是用來分配空間的,所以子類不僅需要呼叫自己的operator new,也需要呼叫基類的operator new,所以把operator new設定為protected就足以對外隱藏noheap的所有子類的operator new了,導致所有的子類都無法在堆上分配。

首先需要明確的是,前面所講的都是在編譯期對於程式設計師使用乙個型別的方法的約束#,當然在執行期是沒辦法做這個約束了(畢竟程式都跑起來了),不過有沒有辦法判斷乙個物件到底是在堆上還是在棧上呢?可以試試下面這個類:

class detect 

void check(int *i)

else

}};

在detect的建構函式中分配乙個區域性變數i,再在呼叫的check函式中分配區域性變數j,如果detect在棧上分配,那麼典型的情況應該像這樣:

this ----> detect() ---> int i ---> check() ---> int j

高位址------------------------------------------>低位址

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

可以用sfinae手法來判斷乙個類是否有destroy成員函式,這麼做有什麼作用呢?

template struct hasdestroy

;

實際使用中,只需要如下呼叫就可以了:

bool res = hasdestroy::value;

只能動態 靜態分配類物件

只能動態分配類物件.cpp 定義控制台應用程式的入口點。include stdafx.h include using namespace std 只在堆上建立 把析構函式設定為私有即可,但是這樣的話以它為基類的派生類就不能訪問析構函式來釋放資源了 因此設定成protect 只能在類內或派生類內訪問。...

C 如何實現類物件只能動態分配或只能靜態分配

c 中建立類的物件有兩種方式 1 靜態建立,例如 a a 靜態建立乙個類物件,就是由編譯器為物件在棧空間中分配記憶體。使用這種方法,是直接呼叫類的建構函式。2 動態建立,例如 a p new a 動態建立乙個類物件,就是使用new運算子為物件在堆空間中分配記憶體。這個過程分為兩步 第一步執行oper...

Redis 怎麼讓相關鍵都分配到集群中的同乙個節點

為什麼有時候要讓相關鍵都分配到同乙個節點?因為如果命令涉及到多個鍵,那麼只有這些鍵都位於同乙個節點裡,redis才能正常支援該命令。redis是按照什麼路由規則,分配鍵到節點的?因為redis集群內建插槽為16384個,所以redis會將每個鍵的鍵名的有效部分使用crc16演算法計算出雜湊值,然後對...