new的三种形态
C++語言一直被認為是復雜編程語言中的杰出代表之一,不僅僅是因為其繁縟的語法規(guī)則,還因為其晦澀的術語。下面要講的就是你的老熟人—new:
它是一個內存管理的操作符,能夠從堆中劃分一塊區(qū)域,自動調用構造函數(shù),動態(tài)地創(chuàng)建某種特定類型的數(shù)據(jù),最后返回該區(qū)域的指針。該數(shù)據(jù)使用完后,應調用delete運算符,釋放動態(tài)申請的這塊內存。
如果這就是你對new的所有認識,那么我不得不說,你依舊被new的和善外表所蒙蔽著。看似簡單的new其實有著三種不同的外衣。
是的,你沒有看錯,也不用感到驚奇,一個簡單的new確實有三種不同的形態(tài),它扮演著三種不同的角色,如下所示:
?
?
下面的代碼片段展示的是我們印象中熟悉的那個new:
?
這里所使用的new是它的第一種形態(tài)new operator。它與sizeof有幾分類似,它是語言內建的,不能重載,也不能改變其行為,無論何時何地它所做的有且只有以下三件事,如圖3-2所示。
| ? |
| 圖3-2 new operator所完成的三件事 |
所以當寫出“string *pStr = new string("Memory Management");”代碼時,它其實做的就是以下幾件事:
其實new operator背后還藏著一個秘密,即它在執(zhí)行過程中,與其余的兩種形態(tài)都發(fā)生了密切的關系:第一步的內存申請是通過operator new完成的;而在第二步中,關于調用什么構造函數(shù),則由new的另外一種形態(tài)placement new來決定的。
對于new的第二種形態(tài)—內存申請中所調用的operator new,它只是一個長著“明星臉”的普通運算符,具有和加減乘除操作符一樣的地位,因此它也是可以重載的。
operator new在默認情況下首先會調用分配內存的代碼,嘗試從堆上得到一段空間,同時它對事情的結果做了最充分的準備:如果成功則直接返回;否則,就轉而去調用一個new_hander,然后繼續(xù)重復前面過程,直到異常拋出為止。所以如果operator new要返回,必須滿足以下條件之一:
內存成功分配。
拋出bad_alloc異常。
通常,operator new函數(shù)通過以下方式進行聲明:
?
注意,這個函數(shù)的返回值類型是void*,因為這個函數(shù)返回的是一個未經(jīng)處理的指針,是一塊未初始化的內存,它像極了C庫中的malloc函數(shù)。如果你對這個過程不滿意,那么可以通過重載operator new來進行必要的干預。例如:
這里的operator new調用了全局的new來進行內存分配(::operator new(size))。當然這里的全局new也是可以重載的,但是在全局空間中重載void * operator new(size_t size)函數(shù)將會改變所有默認的operator new的行為方式,所以必須十二分的注意。還有一點需要注意的是,正像new與delete一一對應一樣,operator new和operator delete也是一一對應的;如果重載了operator new,那么也得重載對應的operator delete。
最后,要介紹的是new的第三種形態(tài)—placement new。正如前面所說的那樣,placement new是用來實現(xiàn)定位構造的,可以通過它來選擇合適的構造函數(shù)。雖然通常情況下,構造函數(shù)是由編譯器自動調用的,但是不排除你有時確實想直接手動調用,比如對未初始化的內存進行處理,獲取想要的對象,此時就得求助于一個叫做placement new的特殊的operator new了:
?
placement new是標準C++庫的一部分,被聲明在了頭文件中,所以只有包含了這個文件,我們才能使用它。它在文件中的函數(shù)定義很簡單,如下所示:
這就是placement new需要完成的事。細心的你可能會發(fā)現(xiàn),placement new的定義與operator new聲明之間的區(qū)別:placement new的定義多一個void*參數(shù)。使用它有一個前提,就是已經(jīng)獲得了指向內存的指針,因為只有這樣我們才知道該把placement new初始化完成的對象放在哪里。
在使用placement new的過程中,我們看到的卻是"new(p) A(2011)"這樣奇怪的調用形式,它在特定的內存地址上用特定的構造函數(shù)實現(xiàn)了構造一個對象的功能,A(2011)就是對構造函數(shù)A(int a)的顯式調用。當然,如果顯式地調用placement new,那么也得本著負責任的態(tài)度顯式地調用與之對應的placement delete:p->~A();。這部分工作本來可以由編譯器獨自完成的:在使用new operator的時候,編譯器會自動生成調用placement new的代碼,相應的,在調用delete operator時同樣會生成調用析構函數(shù)的代碼。所以,除非特別必要,不要直接使用placement new。但是要清楚,它是new operator的一個不可或缺的步驟。當默認的new operator對內存的管理不能滿足我們的需要,希望自己手動管理內存時,placement new就變得有用了。就像STL中的allocator一樣,它借助placement new來實現(xiàn)更靈活有效的內存管理。
最后,總結一下:
如果是在堆上建立對象,那么應該使用 new operator,它會為你提供最為周全的服務。
如果僅僅是分配內存,那么應該調用operator new,但初始化不在它的工作職責之內。如果你對默認的內存分配過程不滿意,想單獨定制,重載operator new 是不二選擇。
如果想在一塊已經(jīng)獲得的內存里建立一個對象,那就應該用placement new。但是通常情況下不建議使用,除非是在某些對時間要求非常高的應用中,因為相對于其他兩個步驟,選擇合適的構造函數(shù)完成對象初始化是一個時間相對較長的過程。
請記住:
不要自信地認為自己對new很熟悉,要正確區(qū)分new所具有的三種不同形態(tài),并能在合適的情形下選擇合適的形態(tài),以滿足特定需求。
總結
- 上一篇: 从零开始学习Sencha Touch M
- 下一篇: 迷你图标集大集合:5000+ 30套免费