C++ new new[]詳解 C++ new new[]詳解

C++ new new[]詳解

 
精髓:
operator new()完成的操作一般只是分配內存;而構造函數的調用(如果需要)是在new運算符中完成的。
operator new和new 運算符是不同的,operator new只分配內存,而只要new出現無論是不是operator new都會調用new運算符從而調用析構函數。

例子是:

複製代碼
#ifndef __PLACEMENT_NEW_INLINE  

#define __PLACEMENT_NEW_INLINE  

inline void *__cdecl operator new(size_t, void *_P)  

        {return (_P); }  

#if     _MSC_VER >= 1200  

inline void __cdecl operator delete(void *, void *)  

    {return; }  

#endif

#endif
複製代碼

 

 

這是new.h下的源代碼,可見這個placement new(一種operator new)只是簡單的返回了地址而已,並沒有調用構造函數,這就說明,構造函數是只要出現new就必定會調用的事實。
我們可以使用如下技術來在制定的內存上調用構造:
  1. A* s = new(p) A(XXX);
注意:這個new是placement new不分配內存,僅僅只call new運算符調用構造函數。

詳解:

A* a = new A;

 我們知道這裏分爲兩步:1.分配內存,2.調用A()構造對象.
事實上,分配內存這一操作就是由operator new(size_t)來完成的,如果類A重載了operator new,那麼將調用A::operator new(size_t ),如果沒有重載,就調用::operator new(size_t ),全局new操作符由C++默認提供。

 operator new的三種形式:

operator new有三種形式:
throwing (1)
void* operator new (std::size_t size) throw (std::bad_alloc);
nothrow (2)
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
placement (3)
void* operator new (std::size_t size, void* ptr) throw();
(1)(2)的區別僅是是否拋出異常,當分配失敗時,前者會拋出bad_alloc異常,後者返回null,不會拋出異常。它們都分配一個固定大小的連續內存。
用法示例:
A* a = new A; //調用throwing(1)
A* a = new(std::nothrow) A; //調用nothrow(2)
(3)是placement new,它也是對operator new的一個重載,定義於<new>中,它多接收一個ptr參數,但它只是簡單地返回ptr。
它可以實現在ptr所指地址上構建一個對象(通過調用其構造函數),這在內存池技術上有廣泛應用。
new(p) A();// 這個操作實際上是先調用了 operator new,這個operator new就是placement new(注意placement new不會分配內存),然後在p處調用了構造函數
前面說到,new運算符都會調用operator new,而這裏的operator new(size_t, void*)並沒有什麼作用,真正起作用的是new運算符的第二個步驟:在p處調用A構造函數。這裏的p可以是動態分配的內存,也可以是棧中緩衝,如char buf[100]; new(buf) A();
placement new的主要作用只是將p放入ecx,並且調用其構造函數。

c++爲什麼定義了析構函數的類的operator new[]傳入的參數會多4字節?
複製代碼
class A

{

public:

A()

{

std::cout<<"call A constructor"<<std::endl;

}

~A()

{

std::cout<<"call A destructor"<<std::endl;

}

void* operator new(size_t size)

{

std::cout<<"call A::operator new[] size:"<<size<<std::endl;

return malloc(size);

}

void operator delete[](void* p)

{

std::cout<<"call A::operator delete[]"<<std::endl;

free(p);

}

void operator delete(void* p)

{

free(p);

}

};

//cpp
#include <iostream>
#include "A.h" void* operator new[](size_t size) { std::cout<<"call global new[] size: "<<size<<std::endl; return malloc(size); } void operator delete[](void* p) { std::cout<<"call global delete[] "<<std::endl; } int _tmain(int argc, _TCHAR* argv[]) { std::cout<<"sizeof A "<<sizeof(A)<<std::endl; A* p1 = new A[3]; delete []p1; system("pause"); return 0; }
複製代碼

 

 

注意:只要在類中重載了new[]運算,就可以使用new A[n]來動態new數組,並且按照自定義的new方式來分配內存。但是他不會去調用自定義的new因爲這兩個運算符沒有關聯。實現的時候應該像如下實現:
複製代碼
1 void * Time::operator new[](size_t size)//重載new[]()運算符,以分配數組
2 {
3 
4 std::cout<<"operator new[]() is called.Object size is "<<size<<std::endl;
5 
6 return malloc(size);//?//在自由存儲中分配內存
7 
8 }
複製代碼

 

使用malloc連續分配數組需要的所有內存,而不是一個一個的來分配。即這個size等於一數組對象的所需要的內存量。
注意:如果所分配的對象顯示定義了析構函數,那麼size會比對象個數X對象佔用內存4字節+對齊字節數,這裏暫時不考慮字節對齊數,只考慮那四字節:
這四個字節是用來記錄數組長度的,方便在delete的時候(這裏不用在delete重載中顯示寫出來,因爲delete包含了一種運算符屬性,只要出現delete有析構,必定調用析構)對每一個對象調用一次析構。要確定對多大的內存塊進行析構就是下面的算式決定:
  1. (size - 4)/(那多出來四字節的值)
很明顯,那4字節的值等於調用析構函數的次數。
如果沒有顯示定義析構的話,就不會多那4字節,在刪除那段申請出來的連續內存時,由於不用調用析構直接全部抹掉即可。
注意:不同的平臺上編譯器的實現都是不同的,所以是不是有那4字節都不一定。


下面是測試代碼:
複製代碼
  1 #include<iostream>
  2 void* operator new[](size_t size)
  3 
  4 {
  5 
  6   std::cout<<"call global new[] size: "<<size<<std::endl;
  7 
  8   return malloc(size);
  9 
 10 }
 11 
 12 class Time
 13 
 14 {
 15 
 16 private:
 17 
 18   int hrs,mins,secs;//時,分,秒
 19 
 20 public:
 21 
 22   Time(int hrs=19,int mins=35,int secs=20);//默認參數的帶參構造函數
 23 
 24   ~Time();//析構函數
 25 
 26   void showTime()const;
 27 
 28   Time operator ++();//重載前綴遞增運算符,++x
 29 
 30   Time operator ++(int);//重載後綴遞增運算法,x++
 31 
 32   bool operator ==(const Time &)const;//重載相等性運算符
 33 
 34   Time & operator =(const Time &);//重載賦值運算符
 35 
 36   void * operator new(size_t size);//重載new()運算符,如:int * pInt=new int(0);
 37 
 38   void operator delete(void * ptr);//重載delete()運算符,如:delete pInt;
 39 
 40   void * operator new[](size_t size);//重載new[]()運算符,以分配數組
 41 
 42   void operator delete[](void * ptr);//重載delete[]()運算符,以去配數組,釋放數組所佔內存
 43 
 44 };
 45 
 46 Time::Time(int hrs,int mins,int secs)
 47 
 48 {
 49 
 50   this->hrs=hrs;
 51 
 52   this->mins=mins;
 53 
 54   this->secs=secs;
 55 
 56   std::cout<<"Time類默認參數的帶參構造函數 "<<(this->hrs)<<':'<<(this->mins)<<':'<<(this->secs)<<std::endl;
 57 
 58 }
 59 
 60  
 61 
 62 Time::~Time()
 63 
 64 {
 65 
 66   std::cout<<"Time類析構函數 "<<(this->hrs)<<':'<<(this->mins)<<':'<<(this->secs)<<std::endl;
 67 
 68 }
 69 
 70  
 71 
 72 void Time::showTime()const
 73 
 74 {
 75 
 76   std::cout<<"Time類showTime()const函數 "<<(this->hrs)<<':'<<(this->mins)<<':'<<(this->secs)<<std::endl;
 77 
 78 }
 79 
 80 Time Time::operator ++()//重載前綴遞增運算符,++x
 81 
 82 {
 83 
 84   secs++;
 85 
 86   if(secs>=60)
 87 
 88   {
 89 
 90     secs=0;
 91 
 92     mins++;
 93 
 94     if(mins>=60)
 95 
 96     {
 97 
 98       mins=0;
 99 
100       hrs++;
101 
102       if(hrs>=24)
103 
104       {
105 
106         hrs=0;
107 
108       }
109 
110     }
111 
112   }
113 
114   return Time(hrs,mins,secs);//返回無名臨時對象
115 
116 }
117 
118 Time Time::operator ++(int)//重載後綴遞增運算法,x++
119 
120 {
121 
122   Time temp(hrs,mins,secs);//生成臨時對象,並進行初始化
123 
124   ++secs;
125 
126   if(secs>=60)
127 
128   {
129 
130     secs=0;
131 
132     mins++;
133 
134     if(mins>=60)
135 
136     {
137 
138       mins=0;
139 
140       hrs++;
141 
142       if(hrs>=24)
143 
144       {
145 
146         hrs=0;
147 
148       }
149 
150     }
151 
152   }
153 
154   return temp;
155 
156 }
157 
158 bool Time::operator ==(const Time & aTime)const//重載相等性運算符
159 
160 {
161 
162   return ((hrs==aTime.hrs)&&(mins==aTime.mins)&&(secs==aTime.secs));
163 
164 }
165 
166 Time & Time::operator =(const Time & aTime)//重載賦值運算符
167 
168 {
169 
170   hrs=aTime.hrs;
171 
172   mins=aTime.mins;
173 
174   secs=aTime.secs;
175 
176   std::cout<<"Time類賦值運算符函數 "<<(this->hrs)<<':'<<(this->mins)<<':'<<(this->secs)<<std::endl;
177 
178   return (*this);//返回當前對象的引用
179 
180 }
181 
182 void * Time::operator new(size_t size)//重載new()運算符,如:int * pInt=new int();
183 
184 {
185 
186   std::cout<<"operator new() is called.Object size is "<<size<<std::endl;
187 
188   return malloc(size);//?//在自由存儲中分配內存
189 
190 }
191 
192 void Time::operator delete(void * ptr)//重載delete()運算符,如:delete pInt;
193 
194 {
195 
196   std::cout<<"operator delete() is called"<<std::endl;
197 
198   free(ptr);//在自由存儲中釋放內存
199 
200 }
201 
202 void * Time::operator new[](size_t size)//重載new[]()運算符,以分配數組
203 
204 {
205 
206   std::cout<<"operator new[]() is called.Object size is "<<size<<std::endl;
207 
208   return malloc(size);//?//在自由存儲中分配內存
209 
210 }
211 
212 void Time::operator delete[](void * ptr)//重載delete[]()運算符,以去配數組,釋放數組所佔內存
213 
214 {
215 
216   std::cout<<"operator delete[]() is called"<<std::endl;
217 
218   free(ptr);//在自由存儲中釋放內存
219 
220 }
221 
222  
223 
224 int main()
225 
226 {
227 
228   Time * pTime;
229 
230   pTime=new Time;//重載new()運算符,調用默認構造函數
231 
232   pTime->showTime();
233 
234   delete pTime;//重載delete()運算符
235 
236   pTime=new Time[3];//重載new[]()運算符,以分配數組,調用默認構造函數
237 
238   delete [] pTime;//重載delete[]()運算符,以去配數組,釋放數組所佔內存
239 
240   getchar();
241 
242   return 0;
243 
244 }
複製代碼
k

對new的幾種形式的一些認識

 
8 篇文章0 訂閱
一道考題:請說說已經有malloc函數了爲何還要引進new?

答:當用在內置數據類型或者結構時,malloc能滿足我們的需要,但用在類類型時不能,這就需要引用new,

new既分配堆內存,又自動調用類的構造函數來創建對象。

是本教科書都有提到上面這一點,可是這些公司爲何還樂此不疲的考到這到道題呢?事情不是這麼簡單,我獻

醜說這幾句。

一、new 與 opeartor new

      首先問大家一個問題:new 與 opeartor new有何區別?
如果你回答:operator new就是new的重載運算符唄!

回答錯誤,new是C++內部定義的一種操作符,總像sizeof一樣是一種操作符,而operator new是實作者定義的

一個全局函數。不信,你可以寫下面的語句:

int *p = operator new(    //這時候,用的是VC的話,就會自動提示該函數的原型。嗯,有七個版本之多。好

,現在我們弄清楚了,一個是操作符,一個是全局函數,而不是原來所以爲的是重載的關係。正同我們看看他

們各有什麼用?

new操作符作了三件事.語句:

      MyClass * p = new MyClass;
的僞碼大至如下:

void *memory = operator new(sizeof(MyClass)); //1調用全局函數: operator new
MyClass::MyClass();                        //2調用構造函數創建對象,如帶參數的話                                           

           //調用的就是帶參構造函數

MyClass * p = static_cast<MyClass*>(memory); //3轉換指針類型,並賦給p


我們可以猜測到真正的內存分配是在operator new函數中完成的。

operator new函數其之的原型是:
void* operator new(size_t size) throw(std::bad_alloc)
返回類型是void* ,    參數類型是:size_t ,是一個在系統頭文件 <cstddef>中定義的typedef。sizeof()的

操作結果就是該類型的。

void* operator new(size_t size)大至如下:

void *p ;
while((p = malloc(size)) == 0)
{
         //內存分配失敗。
      if ( _callnewh(size) == 0)
      {
                _Nomemory();
      }
}

return p;

如果內存分配成功則直接返回所分配的內存的首地址。不然就循環調用if語句,首先判斷 _callnewh(size)調

用是否成功,不成功則調用_Nomemory(),_Nomemory()實際上作用是拋出一個異常,所拋出的是一個標準庫中

定義的std::bad_alloc對象。_callnewh()函數首先會判斷一個全局變量 _pnhHeap是否爲零, _pnhHeap存放的

是一個函數指針,我們可以爲new操作指定一個出錯處理函數,也就是說當new分配內存失敗時就會調用我們剛

才指定的函數,出錯處理函數原型必須是無參且返回類型是void的函數。可以通過全局函數set_new_handler

(new_handler pnew)來設定我們的出錯處理函數,而他又是通過調用_PNH _set_new_handler(_PNH pnh)來實現

設定我們的出錯處理函數的。下面是_PNH _set_new_handler(_PNH pnh)的定義:

_PNH __cdecl _set_new_handler(
              _PNH pnh
              )
{
              _PNH pnhOld;

             
              _mlock(_HEAP_LOCK);

              pnhOld = _pnhHeap;    //把原來在起作用的出錯處理函數的指針賦給pnhOld
              _pnhHeap = pnh;         //我們新設定的出錯處理函數的指針。

             
              _munlock(_HEAP_LOCK);

              return(pnhOld);         //返回原來舊的出錯處理函數的指針。
}


回到我們的_callnewh()函數,如果在MyClass * p = new MyClass;語句之前設定了出錯處理函數,那麼這裏的

_pnhHeap就不爲零,接着就會調用(*_pnhHeap)()即我們的出錯處理函數,否則返回零。接着調用_Nomemory()

拋出異常。這個出錯處理函數是個重要的函數,設計的好的話可以做很多事情,因爲他是在while中被調用的有

多次被調用的機會。在我們的出錯處理函數中如果沒有使用exit(1)等退出程序的語句,也沒有拋出異常的話,

執行完我們的出錯處理函數後,又回到while((p = malloc(size)) == 0),再次分配內存並判斷,還是失敗的

話,再次調用我們的出錯處理函數,當然這個出錯處理函數和上面的那個出錯處理函數不一樣了,因爲我們可

以在上面那個出錯處理函數中調用set_new_handler(_PNH pnh)重新設定一個出錯處理函數,也就是我們第二次

調用的這個出錯處理函數。還不行的話,可以繼續循環,直到你滿意爲止。如果你覺得累了,不玩了,最後就

會調用_Nomemory()拋出異常,函數返回到new調用的地方。

    好,現在我們清楚了操作符new作了三件事,首先調用全局operator new函數, 後者通過調用傳統的malloc

函數分配內存,如果成功直接返回,不然,判斷出錯處理函數是否爲零,不爲零的話,調用我們的出錯處理函

數。否則調用_Nomemory()拋出異常。如果p = malloc(size)成功,new接着做第二件事,創建對象,最後轉換

指針類型並返回。

我們可以重寫operator new函數。當編譯器看到語句MyClass * p = new MyClass;

首先會檢查我們的類定義看是否提供有operator new函數,如有,則調用該函數,接着調用構造函數,轉換類

型並返回。如果沒有重寫operator new函數,則new操作符會調用全局中的那個operator new函數,也就是我們

上面說的這個函數。但是如果我們在new操作符前面限定了::即這樣寫 ::new MyClass則編譯器不會去檢查我們

的類的定義而直接調用全局的operator new函數。

      操作符new不可以重載,就像sizeof操作符一樣是不可以重載的。我們重載的是operator new函數。所以有

一些限定,我們重載的operator new函數的返回類型必須是void*,第一個參數必須是size_t類型的。下面是一

個自定義的operator new函數:

class MyClass
{
    public:
         MyClass()
         {
           cout << "MyClass::MyClass()" << endl;
          }

      static void* operator new(size_t size);

    ~MyClass()
    {
         cout << "MyClass::~MyClass()" << endl;
    }
};

void* MyClass::operator new(size_t size)
{
     //在這裏可以對類的靜態成員數據做些控制。我們在這裏有一句輸出語句代替。
    
     cout << "MyClass::operator new" << endl;
    
     void* p = new MyClass;

     return p;
}
    
這樣寫是行不通的,因爲在MyClass::operator new中的void* p = new MyClass的new是操作符new,他做三件

事,第一件就是調用MyClass::operator new(size_t size),所以這裏
是遞歸調用了。把程序改成:

void* MyClass::operator new(size_t size)
{
     //在這裏可以對類的靜態成員數據做些控制。我們在這裏有一句輸出語句代替。
     cout << "MyClass::operator new" << endl;
    
     void* p = operator new(size);    //已修改。

     return p;
}

這樣還是不行的,這樣是直接遞歸(自己調用自己)。剛纔是間接遞歸。應該改成:void* p = ::operator new

(size);    OK,使用的是全局中的operator new,或者寫成:void* p = malloc( size),只是這樣一來,出錯後

不會自動調用出錯處理函數了,只會簡單的返回NULL,所以在使用new操作符的地方要注意先檢測返回值是否爲

零,所以最好不用malloc,還是用:: operator new(size)好,這裏還可以用void* p = new char[size],用的

是new[]操作符,不會兩次調用構造函數,也不會造成遞歸。只是要注意在我們重寫的operator delete函數中

要調用delete[] 後釋放。一般情況下,我們重寫了operator new函數,都要重寫operator delete函數,而且

後者中的釋放資源的函數要與前者分配資源的函數的形式要搭配。


另外,要想把自己重寫的operator new函數設計得好,還是有好些地方需要注意的。好在需要我們重寫這個函

數的情況不多,真正需要重寫時,還是先參考些這方面的資料纔行,<effective c++>一書中就有相關的知識介

紹。在這裏我只是提到一下,讓大家知道有這麼一回事,應付一下這道公司們樂此不疲的考題。洋洋灑灑寫上

上千字,小樣,看你還敢不敢考這樣的考題。


二、new[] 與 operator new[]

    new[]操作符與new差不多,同樣做三件事:調用operator new[]函數,歷遍一個vector調用構造函數。轉換

指向首地址的指針類型並返回。

operator new[]函數通過把操作符new A[number]中的A與number進行計算:size_t count = number * sizeof

(A), 然後調用全局函數operator new(count).

三 new(void*) Myclass 與 operator new(size_t, void*)
    
     指定位置創建操作符new()同樣做三件事,第一件就是調用operator new(size_t, void*)函數,下面兩件和

new操作符的最後二件事是一樣的。讓我們來看看vs.net中operator new(size_t, void*)的定義:

inline void *__cdecl operator new(size_t, void *_Where) _THROW0()
      {      // construct array with placement at _Where
      return (_Where);
      }

和operator new相比好簡單哦,我們看到了,他並沒有調用malloc函數,也沒有調用operator new函數,他怎

麼分配的內存啊?!對於operator new函數,他通過循環調用malloc函數來分配一塊內存,最好把這塊分配好的

內存return p返回給操作符new,讓他在上面做第二,第三件事。我們這裏return (_Where);按此推理,_Where
6應該指向一塊已分配的可使用的內存。_Where從那裏來的啊?答案是使用操作符new(void* _Where) MyClass

時所指定的。這就是指定位置創建操作符new()的用法,先在別處分配好一塊內存,然後把這塊內存的首地址做

爲參數調用new(),new()就會在這塊指定位置上創建對象,然後再把這塊指定的內存的首地址自制一份給p,接

着轉換類型並返回。這樣子操作符new()並沒有真正分配內存,所以不能調用delete來釋放內存。當程度使用共

享內存或者memory-mapped I/O指定位置創建就比較有用,因爲在這樣的程序裏對象必須放置在一個確定的地址

上或者一塊被例程分配的內存裏。下面看個例子。


#include <iostream>
#include <new>                                   //要使用指定位置創建操作符發佈包含該頭文件。
using namespace std;

void* mallocShared(size_t size);    //用於分配共享內存,該函數是別的程序員寫的,你只知道通過
                                                           //調用他可以獲得一塊已分配而未初始化的內存。

class A
{
     public:
     A()
     {
          cout << "A::A()" << endl;
          m_n = 0;
     }

     int Get()
     {
          return m_n;
     }

     ~A()
     {
          cout << "A::~A()" << endl;
     }

private:
int m_n;
};

int main()
{
      void* p = mallocShared(sizeof(A)); //該句也有可能是在別的地方調用的,
                                                                 //然後把p傳過來。這裏爲了簡化而放在此調用

      A* pA = new(p) A;

      cout << pA->Get() << endl;

      delete pA;

      return 0;
}







void* mallocShared(size_t size)
{
      void* p = malloc(size);
    
      if ( p == NULL)
      {
              cerr << "mallocShared(size_t size) failed!" << endl;

              exit(1);
      }

      return p;
}

這個程序有問題嗎?拋開設計上的好壞不說,就說這個程序能否通過編譯?如能,運行結果如何?請先思考三

分鐘再往下看。









有疑問的可能是這一句delete pA; 函數mallocShared不是你寫的,你不知道其內部是通過什麼形式分配的內存

,就調用delete來釋放能行嗎?假如又讓我們知道他內部是通過malloc函數來分配的內存,用delete來釋放能

行嗎?答案是:能通過編譯,並能得出正確結果,通過malloc函數來分配的內存用delete來釋放是沒問題的,

new操作符不也是通過malloc來分配的內存,同樣可以用delete來釋放啊。如果mallocShared是下面這樣子,情

況又怎麼樣呢?

void* mallocShared(size_t size)
{
    
      return (new char[size]);
}

結果是和上面是一樣的,delete p沒問題,只要用new[]來分配的內存塊的大小和用delete釋放的內存的大小是

一樣的就沒問題。回想一下看,我們用new A來分配內存時,實際上是通過operator new(size)來分配的,這裏

的size = sizeof(A),這種情況下我們可以用 delete p來釋放,只要p 的類型是A*,因爲delete 是通過調用

operator delete(size)函數來釋放的內存,這裏的size也是等於sizeof(P),當我們調用new char[number]時

,先調用operator new[]函數,後者實際上也是調用 operator new(number* sizeof(char))來分配的內存,既

然都是通過調用operator new來分配的內存,所以調用delete 來釋放應該也是沒問題的。因爲這

裏:mallocShared(sizeof(A)) ---->    size == sizeof(A) == size* sizeof(char) == sizeof(P) 大小一樣。
改成:

void* mallocShared(size_t size)
{
    
      return (operator new(size));
}

結果也是一樣的! 當然這裏是假設沒有提供自己的operator new函數的情況下,如果重寫了operator new函數

就要改成:

void* mallocShared(size_t size)
{
    
      return (::operator new(size));     //用的是全局函數
}

這個程序目前沒問題,但是存在很多的安全隱患,很容易就出錯,一不小心就陰溝裏翻船,有“未定義”行爲

產生,結果是啥事都有可能發生。應該謝絕寫這樣的程序。

上面說了mallocShared(sizeof(A));可能不是在你的程序中調用的,而是在別人那裏調用的,然後別人給你傳

來一個指針讓你把這個指針作爲參數調用你自己的A* pA = new(p) A,這樣子你調用delete p來釋放,別人那裏

或者還需要用,因爲這是共共享的內存,又或者別人那裏在做完想要做的工作之後,調用delete ,這樣子就出

問題了。同一塊內存不能釋放兩次。就算mallocShared(sizeof(A));是在你這裏調用的,那麼你可以算是內存

的分配者,你有權利and義務把他釋放,可是你也要先確定別人還需不需要用到這塊內存,需要的話,你就不能

馬上delete,又或者呆會你自己也還需要用到,再次在這塊內存上指定位置創建。所以就不必再調用

mallocShared(sizeof(A));來分配內存。把主函數改成下面這樣,結果又如何,能通過編譯嗎?

int main()
{
      void* p = mallocShared(sizeof(A)); //就限定是在這裏調用的。

      A* pA = new(p) A;

      cout << pA->Get() << endl;

      A* pA1 = new(p) A;                             //再次指定位置創建。



      delete pA;

      return 0;
}

答案是:能通過編譯,運行結果如下:

A::A()
0
A::A()
~A::A()

原來的那塊內存是確實被釋放掉了的,只是這裏構造函數A::A()調用了兩次而析構函數A::~A()只調用一次,
這顯然不太好,如果你的類在其他地方分配了資源,需要通過析構函數來釋放,這樣子你少調用了一次析構函

數就會造成內存泄漏或者別的問題了。所以應該把程序改成下面這樣:


int main()
{
      void* p = mallocShared(sizeof(A)); //就限定是在這裏調用的。

      A* pA = new(p) A;

      cout << pA->Get() << endl;

      A* pA1 = new(p) A;                             //再次指定位置創建。

              pA->~A();               //這裏顯式調用析構函數來析構對象,但是內存並沒有釋放,還可以再次使用。

      A* pA1 = new(p) A;

           //在這裏判斷別的程序是否還需要用到該內存

      delete pA;          //當別人不再需要,自己也不會再用到,可以釋放!

      return 0;
}

這句delete p總讓我擔心受怕,最好調用和mallocShared函數相對應的函數來釋放內存,你寫了mallocShared

函數來分配資源就有義務寫一個freeShared函數來釋放資源,分配資源函數和釋放資源函數是一對的,一起提

供給別人使用。因爲只有你自己最清楚你的mallocShared函數是怎麼分配的資源,你的freeShared就應該做相

應的工作。比如在mallocShared中除了分配內存,還用到其他資源,如果直接調用delete p來釋放那就成問題

了。應該調用freeShared來釋放。如果你是老闆而你的員工只寫一個mallocShared函數卻沒有提供相應的

freeShared函數,建議你讓他走人! 不然遲早會出問題的。如果void* p = mallocShared(sizeof(A)); 語句不

是在這裏調用的,你既不能使用delete p,也不能使用freeShared(p),或者其他一切釋放資源的函數。不是你

分配的資源你無權釋放。當然你對整個程序把握得比較好,一切盡在你控制中,而你又和別人有協議由你來釋

放的情況除外。
void * operator new(size_t, void *_Where)同樣可以提供自己的版本,這時候第二個參數可以是別的類型,

_Where也不一定是指向一塊已分配而未使用的內存,可以是一個指向可以分配內存的函數的指針,然後在

operator new(size_t, void *_Where)內部通過該指針來調用函數從而分配內存。也可以是其他東西,不一定

要是指針。總之可以傳遞你想傳遞的東西。下面來看個例子,這個例子來自<Bjarne Stroustrup的FAQ:C++的

風格與技巧>我懶得寫了,就用他寫的這個.

以下是原文:



有沒有“指定位置刪除”(placement delete)?



沒有,不過如果你需要的話,可以自己寫一個。



看看這個指定位置創建(placement new),它將對象放進了一系列Arena中;



class Arena {

public:

void* allocate(size_t);

void deallocate(void*);

// ...

};



void* operator new(size_t sz, Arena& a)

{

return a.allocate(sz);    //梁楊注:這裏第二個參數傳遞的是一個引用,
                                                                  //然後通過其成員函數來分配內存.

}



Arena a1(some arguments);

Arena a2(some arguments);



這樣實現了之後,我們就可以這麼寫:



X* p1 = new(a1) X;

Y* p2 = new(a1) Y;

Z* p3 = new(a2) Z;

// ...



但是,以後怎樣正確地銷燬這些對象呢?沒有對應於這種“placement new”的內建的

“placement delete”,原因是,沒有一種通用的方法可以保證它被正確地使用。在C++的

類型系統中,沒有什麼東西可以讓我們確認,p1一定指向一個由Arena類型的a1分派的對象

。p1可能指向任何東西分派的任何一塊地方。



然而,有時候程序員是知道的,所以這是一種方法:



template<class T> void destroy(T* p, Arena& a)

{

    if (p) {

           p->~T(); // explicit destructor call

           a.deallocate(p);

}

}



現在我們可以這麼寫:



destroy(p1,a1);

destroy(p2,a2);

destroy(p3,a3);



如果Arena維護了它保存着的對象的線索,你甚至可以自己寫一個析構函數,以避免它發生

錯誤。
     這也是可能的:定義一對相互匹配的操作符new()和delete(),以維護《C++程序設計語

言》15.6中的類繼承體系。參見《C++語言的設計和演變》10.4和《C++程序設計語言》

19.4.5。

///



C++中有指定位置創建操作符:new(),但沒有指定位置刪除操作符:delete()。

我們可以寫:
                          X* p1 = new(a1) X;    //指定位置創建
但是不可以寫:
                          delete(a1) p1           //這句不能通過編譯。

他上面寫的這個程序其實是有點問題的(靠,梁楊你也太牛B了吧,C++之父寫的你也敢說有問題?!),用事實說話

,如果只提供&nbsp;void* operator new(size_t sz, Arena& a)而不提供void operator delete(void* , Arena&)
函數,vs.net的編譯器會提出一條警告: “void *operator new(size_t,Arena &)” : 未找到匹配的刪除運

算符;如果初始化引發異常,則不會釋放內存。

對於new操作符會作三件事,在第一件分配內存順利完成之後,接着會調用構造函數,如果在調用構造函數中發

生異常,他就會調用operator delete(void*)函數來釋放在第一件事中通過operator new(size_t)來分配的內

存,從而保證不會發生內存泄漏。同樣定位創建操作符new()也做三件事:第一件調用我們重寫的void*

operator new(size_t sz, Arena& a)來分配內存,這步成功之後接着調用構造函數,那麼如果在調用構造函數

中發生異常時,怎麼辦呢?沒有相應的釋放函數來給編譯器調用,所以我們必須自己提供一個void operator

delete(void* , Arena&)來釋放內存。這個函數第一個參數的類型必須是void*, 第二個類型必須和operator

new()中的相同,不然當發生異常時,編譯器不會調用該函數的。當我們提供了void operator delete(void* ,

Arena&)後還是不能寫:delete(a1) pA1這樣的語句的。可以寫operator delete(pA1, a1),但是最好不要這樣

調用,該函數是專爲編譯器寫的,當發生構造異常時調用的。當我們成功的創建一個對象,做完一些事之後,

應該還象他上面寫的那樣,通過調用destroy(p1,a1);來釋放。

注意這裏內存分配是在指定創建函數operator new()中調用其他函數來分配的,所以我們需要提供一個相應的

delete( )來預防構造失敗時來釋放資源。但是在我們前面寫的那個共享內存的例子,定位創建函數new()並沒

有分配內存,內存分配是在其他地方完成的,這時候我們就不需要提供定位刪除函數delete()來釋放資源,你

構造函數失敗就失敗唄,內存又不是你分配的,你無權釋放,不可能說你創建一個對象失敗了,連內存都釋放

了,呆會有權釋放內存者(分配者有權釋放)再釋放一次,那問題就大了。也就是說當我們重寫定位創建函數

new()時,如果內存是在其中分配的,那麼就要提供相應的delete()函數給編譯器專用。反之則不必提供

delete()函數。



四、new(nothrow) MyClass操作符

從字面就可以看出來了,這個版本的new不會拋出異常。他也做三件事,其中第一件調用的就是不拋出異常的

operator new()函數,其原型是:

void *__cdecl operator new(size_t, const std::nothrow_t&)
      _THROW0();

看到了,其後面的異常規範是空的,即不拋出任何異常。與常用的那個operator new()函數不同

void *__cdecl operator new(size_t) _THROW1(std::bad_alloc); ,這個可以拋出std::bad_alloc異常。
不允許拋出異常不併代其內部也不會發生異常,當內部內存分配失敗時發生異常,因爲規範中不允許拋出異常

,這就會拋出一個意外的異常,如果用戶不對這個意外異常進行捕捉的話,默認處理是調用abort函數。

五、 new const MyClass

這個版本的new表示在堆內存中創建後個const對象,創建完之後就不能再修改,所以必須在創建時初始化(有無

參構造函數的類除外),並且返回的指針是一個指向const對象的指針,即要寫:const int* p = new const

int(1); 而不能寫int* p = new const int(1).
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章