詳解C++中如何將構(gòu)造函數(shù)或析構(gòu)函數(shù)的訪問(wèn)權(quán)限定為private
今天面試被問(wèn)到了這個(gè)單例模式常用到的技術(shù)手段,下面進(jìn)行分析:
很多情況下要求當(dāng)前的程序中只有一個(gè)object。例如一個(gè)程序只有一個(gè)和數(shù)據(jù)庫(kù)的連接,只有一個(gè)鼠標(biāo)的object。通常我們都將構(gòu)造函數(shù)的聲明置于public區(qū)段,假如我們將其放入private區(qū)段中會(huì)發(fā)生什么樣的后果?這意味著什么?
當(dāng)我們?cè)诔绦蛑新暶饕粋€(gè)對(duì)象時(shí),編譯器為調(diào)用構(gòu)造函數(shù)(如果有的話),而這個(gè)調(diào)用將通常是外部的,也就是說(shuō)它不屬于class對(duì)象本身的調(diào)用,假如構(gòu)造函數(shù)是私有的,由于在class外部不允許訪問(wèn)私有成員,所以這將導(dǎo)致編譯出錯(cuò)。
然而,對(duì)于class本身,可以利用它的static公有成員,因?yàn)樗鼈儶?dú)立于class對(duì)象之外,不必產(chǎn)生對(duì)象也可以使用它們。
此時(shí)因?yàn)闃?gòu)造函數(shù)被class私有化,所以我們要?jiǎng)?chuàng)建出對(duì)象,就必須能夠訪問(wèn)到class的私有域;這一點(diǎn)只有class的成員可以做得到;但在我們建構(gòu)出其對(duì)象之前,怎么能利用它的成員呢?static公有成員,它是獨(dú)立于class對(duì)象而存在的,“我們”可以訪問(wèn)得到。假如在某個(gè)static函數(shù)中創(chuàng)建了該class的對(duì)象,并以引用或者指針的形式將其返回(這里不以對(duì)象返回,主要是構(gòu)造函數(shù)是私有的,外部不能創(chuàng)建臨時(shí)對(duì)象),就獲得了這個(gè)對(duì)象的使用權(quán)。
下面是例子:
class OnlyHeapClass { public: static OnlyHeapClass* GetInstance() { // 創(chuàng)建一個(gè)OnlyHeapClass對(duì)象并返回其指針 return (new OnlyHeapClass); } void Destroy(); private: OnlyHeapClass() { } ~OnlyHeapClass() {} }; int main() { OnlyHeapClass *p = OnlyHeapClass::GetInstance(); ... // 使用*p delete p; return 0; }
這個(gè)例子使用了私有構(gòu)造函數(shù),GetInstance()作為OnlyHeapClass的靜態(tài)成員函數(shù)來(lái)在內(nèi)存中創(chuàng)建對(duì)象:由于要跨函數(shù)傳遞并且不能使用值傳遞方式,所以我們選擇在堆上創(chuàng)建對(duì)象,這樣即使getInstance()退出,對(duì)象也不會(huì)隨之釋放,可以手動(dòng)釋放。
構(gòu)造函數(shù)私有化的類的設(shè)計(jì)保證了其他類不能從這個(gè)類派生或者創(chuàng)建類的實(shí)例,還有這樣的用途:例如,實(shí)現(xiàn)這樣一個(gè)class:它在內(nèi)存中至多存在一個(gè),或者指定數(shù)量個(gè)的對(duì)象(可以在class的私有域中添加一個(gè)static類型的計(jì)數(shù)器,它的初值置為0,然后在GetInstance()中作些限制:每次調(diào)用它時(shí)先檢查計(jì)數(shù)器的值是否已經(jīng)達(dá)到對(duì)象個(gè)數(shù)的上限值,如果是則產(chǎn)生錯(cuò)誤,否則才new出新的對(duì)象,同時(shí)將計(jì)數(shù)器的值增1.最后,為了避免值復(fù)制時(shí)產(chǎn)生新的對(duì)象副本,除了將構(gòu)造函數(shù)置為私有外,復(fù)制構(gòu)造函數(shù)也要特別聲明并置為私有。
如果將構(gòu)造函數(shù)設(shè)計(jì)成Protected,也可以實(shí)現(xiàn)同樣的目的,但是可以被繼承。
另外如何保證只能在堆上new一個(gè)新的類對(duì)象呢?只需把析構(gòu)函數(shù)定義為私有成員。
原因是C++是一個(gè)靜態(tài)綁定的語(yǔ)言。在編譯過(guò)程中,所有的非虛函數(shù)調(diào)用都必須分析完成。即使是虛函數(shù),也需檢查可訪問(wèn)性。因些,當(dāng)在棧上生成對(duì)象時(shí),對(duì)象會(huì)自動(dòng)析構(gòu),也就說(shuō)析構(gòu)函數(shù)必須可以訪問(wèn)。而堆上生成對(duì)象,由于析構(gòu)時(shí)機(jī)由程序員控制,所以不一定需要析構(gòu)函數(shù)。保證了不能在棧上生成對(duì)象后,需要證明能在堆上生成它。這里OnlyHeapClass與一般對(duì)象唯一的區(qū)別在于它的析構(gòu)函數(shù)為私有。delete操作會(huì)調(diào)用析構(gòu)函數(shù)。所以不能編譯。
那么如何釋放它呢?答案也很簡(jiǎn)單,提供一個(gè)成員函數(shù),完成delete操作。在成員函數(shù)中,析構(gòu)函數(shù)是可以訪問(wèn)的。當(dāng)然detele操作也是可以編譯通過(guò)。
void OnlyHeapClass::Destroy() { delete this; }
構(gòu)造函數(shù)私有化的類的設(shè)計(jì)可以保證只能用new命令在堆中來(lái)生成對(duì)象,只能動(dòng)態(tài)的去創(chuàng)建對(duì)象,這樣可以自由的控制對(duì)象的生命周期。但是,這樣的類需要提供創(chuàng)建和撤銷的公共接口。
另外重載delete,new為私有可以達(dá)到要求對(duì)象創(chuàng)建于棧上的目的,用placement new也可以創(chuàng)建在棧上。
補(bǔ)充:
1.為什么要自己調(diào)用呢?對(duì)象結(jié)束生存期時(shí)不就自動(dòng)調(diào)用析構(gòu)函數(shù)了嗎?什么情況下需要自己調(diào)用析構(gòu)函數(shù)呢?
比如這樣一種情況,你希望在析構(gòu)之前必須做一些事情,但是用你類的人并不知道, 那么你就可以重新寫一個(gè)函數(shù),里面把要做的事情全部做完了再調(diào)用析構(gòu)函數(shù)。 這樣人家只能調(diào)用你這個(gè)函數(shù)析構(gòu)對(duì)象,從而保證了析構(gòu)前一定會(huì)做你要求的動(dòng)作。
2.什么情況下才用得著只生成堆對(duì)象呢?
堆對(duì)象就是new出來(lái)的,相對(duì)于棧對(duì)象而言。什么情況下要new,什么情況下在棧里面 提前分配,無(wú)非就是何時(shí)該用動(dòng)態(tài),何時(shí)該用靜態(tài)生成的問(wèn)題。這個(gè)要根據(jù)具體情況具體分析。比如你在一個(gè)函數(shù)里面事先知道某個(gè)對(duì)象最多只可能10個(gè),那么你就可以 定義這個(gè)對(duì)象的一個(gè)數(shù)組。10個(gè)元素,每個(gè)元素都是一個(gè)棧對(duì)象。如果你無(wú)法確定數(shù) 字,那么你就可以定義一個(gè)這個(gè)對(duì)象的指針,需要?jiǎng)?chuàng)建的時(shí)候就new出來(lái),并且用list 或者vector管理起來(lái)。
類中“私有”權(quán)限的含義就是:私有成員只能在類域內(nèi)被訪問(wèn),不能在類域外進(jìn)行訪問(wèn)。
把析構(gòu)函數(shù)定義為私有的,就阻止了用戶在類域外對(duì)析構(gòu)函數(shù)的使用。這表現(xiàn)在如下兩個(gè)方面:
1. 禁止用戶對(duì)此類型的變量進(jìn)行定義,即禁止在棧內(nèi)存空間內(nèi)創(chuàng)建此類型的對(duì)象。要?jiǎng)?chuàng)建對(duì)象,只能用 new 在堆上進(jìn)行。
2. 禁止用戶在程序中使用 delete 刪除此類型對(duì)象。對(duì)象的刪除只能在類內(nèi)實(shí)現(xiàn),也就是說(shuō)只有類的實(shí)現(xiàn)者才有可能實(shí)現(xiàn)對(duì)對(duì)象的 delete,用戶不能隨便刪除對(duì)象。如果用戶想刪除對(duì)象的話,只能按照類的實(shí)現(xiàn)者提供的方法進(jìn)行。
可見(jiàn),這樣做之后大大限制了用戶對(duì)此類的使用。一般來(lái)說(shuō)不要這樣做;通常這樣做是用來(lái)達(dá)到特殊的目的,比如在 singleton 的實(shí)現(xiàn)上。
PS:構(gòu)造函數(shù)為什么不能是虛函數(shù)
另外再來(lái)說(shuō)一下構(gòu)造函數(shù)和虛函數(shù)的區(qū)別:
1. 從存儲(chǔ)空間角度,虛函數(shù)對(duì)應(yīng)一個(gè)指向vtable虛函數(shù)表的指針,這大家都知道,可是這個(gè)指向vtable的指針其實(shí)是存儲(chǔ)在對(duì)象的內(nèi)存空間的。問(wèn)題出來(lái)了,如果構(gòu)造函數(shù)是虛的,就需要通過(guò) vtable來(lái)調(diào)用,可是對(duì)象還沒(méi)有實(shí)例化,也就是內(nèi)存空間還沒(méi)有,怎么找vtable呢?所以構(gòu)造函數(shù)不能是虛函數(shù)。
2. 從使用角度,虛函數(shù)主要用于在信息不全的情況下,能使重載的函數(shù)得到對(duì)應(yīng)的調(diào)用。構(gòu)造函數(shù)本身就是要初始化實(shí)例,那使用虛函數(shù)也沒(méi)有實(shí)際意義呀。所以構(gòu)造函數(shù)沒(méi)有必要是虛函數(shù)。虛函數(shù)的作用在于通過(guò)父類的指針或者引用來(lái)調(diào)用它的時(shí)候能夠變成調(diào)用子類的那個(gè)成員函數(shù)。而構(gòu)造函數(shù)是在創(chuàng)建對(duì)象時(shí)自動(dòng)調(diào)用的,不可能通過(guò)父類的指針或者引用去調(diào)用,因此也就規(guī)定構(gòu)造函數(shù)不能是虛函數(shù)。
3. 構(gòu)造函數(shù)不需要是虛函數(shù),也不允許是虛函數(shù),因?yàn)閯?chuàng)建一個(gè)對(duì)象時(shí)我們總是要明確指定對(duì)象的類型,盡管我們可能通過(guò)實(shí)驗(yàn)室的基類的指針或引用去訪問(wèn)它但析構(gòu)卻不一定,我們往往通過(guò)基類的指針來(lái)銷毀對(duì)象。這時(shí)候如果析構(gòu)函數(shù)不是虛函數(shù),就不能正確識(shí)別對(duì)象類型從而不能正確調(diào)用析構(gòu)函數(shù)。
4. 從實(shí)現(xiàn)上看,vbtl在構(gòu)造函數(shù)調(diào)用后才建立,因而構(gòu)造函數(shù)不可能成為虛函數(shù)從實(shí)際含義上看,在調(diào)用構(gòu)造函數(shù)時(shí)還不能確定對(duì)象的真實(shí)類型(因?yàn)樽宇悤?huì)調(diào)父類的構(gòu)造函數(shù));而且構(gòu)造函數(shù)的作用是提供初始化,在對(duì)象生命期只執(zhí)行一次,不是對(duì)象的動(dòng)態(tài)行為,也沒(méi)有必要成為虛函數(shù)。
5. 當(dāng)一個(gè)構(gòu)造函數(shù)被調(diào)用時(shí),它做的首要的事情之一是初始化它的VPTR。因此,它只能知道它是“當(dāng)前”類的,而完全忽視這個(gè)對(duì)象后面是否還有繼承者。當(dāng)編譯器為這個(gè)構(gòu)造函數(shù)產(chǎn)生代碼時(shí),它是為這個(gè)類的構(gòu)造函數(shù)產(chǎn)生代碼——既不是為基類,也不是為它的派生類(因?yàn)轭惒恢勒l(shuí)繼承它)。所以它使用的VPTR必須是對(duì)于這個(gè)類的VTABLE。而且,只要它是最后的構(gòu)造函數(shù)調(diào)用,那么在這個(gè)對(duì)象的生命期內(nèi),VPTR將保持被初始化為指向這個(gè)VTABLE, 但如果接著還有一個(gè)更晚派生的構(gòu)造函數(shù)被調(diào)用,這個(gè)構(gòu)造函數(shù)又將設(shè)置VPTR指向它的 VTABLE,等.直到最后的構(gòu)造函數(shù)結(jié)束。VPTR的狀態(tài)是由被最后調(diào)用的構(gòu)造函數(shù)確定的。這就是為什么構(gòu)造函數(shù)調(diào)用是從基類到更加派生類順序的另一個(gè)理由。但是,當(dāng)這一系列構(gòu)造函數(shù)調(diào)用正發(fā)生時(shí),每個(gè)構(gòu)造函數(shù)都已經(jīng)設(shè)置VPTR指向它自己的VTABLE。如果函數(shù)調(diào)用使用虛機(jī)制,它將只產(chǎn)生通過(guò)它自己的VTABLE的調(diào)用,而不是最后的VTABLE(所有構(gòu)造函數(shù)被調(diào)用后才會(huì)有最后的VTABLE)。
- C++類成員構(gòu)造函數(shù)和析構(gòu)函數(shù)順序示例詳細(xì)講解
- 深入解析C++中的構(gòu)造函數(shù)和析構(gòu)函數(shù)
- 深入C++中構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、賦值操作符、析構(gòu)函數(shù)的調(diào)用過(guò)程總結(jié)
- C++函數(shù)返回值為對(duì)象時(shí),構(gòu)造析構(gòu)函數(shù)的執(zhí)行細(xì)節(jié)
- 詳解C++ 編寫String 的構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、析構(gòu)函數(shù)和賦值函數(shù)
- C++中構(gòu)造函數(shù)與析構(gòu)函數(shù)的調(diào)用順序詳解
- c++基礎(chǔ)語(yǔ)法:構(gòu)造函數(shù)與析構(gòu)函數(shù)
- C++構(gòu)造函數(shù)和析構(gòu)函數(shù)的使用與講解
- 正確理解C++的構(gòu)造函數(shù)和析構(gòu)函數(shù)
- C++踩坑實(shí)戰(zhàn)之構(gòu)造和析構(gòu)函數(shù)
相關(guān)文章
C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之單鏈表的實(shí)現(xiàn)
鏈表是一種物理存儲(chǔ)結(jié)構(gòu)上非連續(xù)、非順序的存儲(chǔ)結(jié)構(gòu),數(shù)據(jù)元素的邏輯順序是通過(guò)鏈表中的指針鏈接次序?qū)崿F(xiàn)的。本文將用C語(yǔ)言實(shí)現(xiàn)單鏈表,需要的可以參考一下2022-06-06C語(yǔ)言fprintf()函數(shù)和fscanf()函數(shù)的具體使用
本文主要介紹了C語(yǔ)言fprintf()函數(shù)和fscanf()函數(shù)的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11C語(yǔ)言之沒(méi)有main函數(shù)的helloworld示例
這篇文章主要介紹了C語(yǔ)言之沒(méi)有main函數(shù)的helloworld示例,本文分解了帶main函數(shù)的helloworld示例,從而分析出不需要main函數(shù)的helloworld示例,需要的朋友可以參考下2015-03-03關(guān)于C/C++中typedef的定義與用法總結(jié)
在C還是C++代碼中,typedef都使用的很多,在C代碼中尤其是多,typedef與#define有些相似,其實(shí)是不同的,特別是在一些復(fù)雜的用法上,需要的朋友可以參考下2012-12-12C語(yǔ)言 詳細(xì)分析結(jié)構(gòu)體的內(nèi)存對(duì)齊
C 數(shù)組允許定義可存儲(chǔ)相同類型數(shù)據(jù)項(xiàng)的變量,結(jié)構(gòu)是 C 編程中另一種用戶自定義的可用的數(shù)據(jù)類型,它允許你存儲(chǔ)不同類型的數(shù)據(jù)項(xiàng),本篇讓我們來(lái)了解C 的結(jié)構(gòu)體內(nèi)存對(duì)齊2022-03-03C++ 實(shí)現(xiàn)求最大公約數(shù)和最小公倍數(shù)
這篇文章主要介紹了c++ 實(shí)現(xiàn)求最大公約數(shù)和最小公倍數(shù)的相關(guān)資料,需要的朋友可以參考下2017-05-05Qt實(shí)現(xiàn)網(wǎng)絡(luò)聊天室的示例代碼
本文主要介紹了Qt實(shí)現(xiàn)網(wǎng)絡(luò)聊天室,實(shí)現(xiàn)一個(gè)在線聊天室, 使用tcp對(duì)客戶端和服務(wù)器端進(jìn)行通訊。具有一定的參考價(jià)值,具有一定的參考價(jià)值,2021-06-06