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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > c/c++ >内容正文

c/c++

深入C++的new

發(fā)布時(shí)間:2024/4/11 c/c++ 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入C++的new 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

new操作符(new operator) 和 new操作(operator new)的區(qū)別。

當(dāng)你寫這樣的代碼: string *ps = new string("Memory Management");?
你使用的 new 是?new 操作符。這個(gè)操作符就象 sizeof 一樣是語(yǔ)言內(nèi)置的,你不能改變它的含義,它的功能總是一樣的。它要完成的功能分成兩部分。第一部分是分配足夠的內(nèi)存以便
容納所需類型的對(duì)象。第二部分是它調(diào)用構(gòu)造函數(shù)初始化內(nèi)存中的對(duì)象。new操作符總是做這兩件事情,你不能以任何方式改變它的行為。

?

你所能改變的是如何為對(duì)象分配內(nèi)存。new 操作符調(diào)用一個(gè)函數(shù)來(lái)完成必需的內(nèi)存分配,你能夠重寫或重載這個(gè)函數(shù)來(lái)改變它的行為。new 操作符為分配內(nèi)存所調(diào)用函數(shù)的名字是 operator new

?

函數(shù) operator new 通常這樣聲明:?
void * operator new(size_t size); //參數(shù) size_t確定分配多少內(nèi)存
返回值類型是 void*,因?yàn)檫@個(gè)函數(shù)返回一個(gè)未經(jīng)處理(raw)的指針,未初始化的內(nèi)存

你一般不會(huì)直接調(diào)用 operator new,但是一旦這么做,你可以象調(diào)用其它函數(shù)一樣調(diào)用它:?
void *rawMemory = operator new(sizeof(string));

操作符operator new將返回一個(gè)指針, 指向一塊足夠容納一個(gè)string類型對(duì)象的內(nèi)存。?
就象 malloc 一樣,operator new 的職責(zé)只是分配內(nèi)存。它對(duì)構(gòu)造函數(shù)一無(wú)所知。operator new所了解的是內(nèi)存分配

void *memory = // 得到未經(jīng)處理的內(nèi)存
operator new(sizeof(string)); // 為 String對(duì)象?
call string::string("Memory Management") //初始化?
on *memory; // 內(nèi)存中 // 的對(duì)象?
string *ps = // 是 ps指針指向?
static_cast<string*>(memory); // 新的對(duì)象

導(dǎo)讀:?
  返回值類型是void*,表示其返回的是一個(gè)未經(jīng)處理(raw)的指針,指向未初始化的內(nèi)存。參數(shù)size_t確定分配多少內(nèi)存。你能增加額外的參數(shù)重載函數(shù)operator new,但是第一個(gè)參數(shù)類型必須是size_t。頭文件中有一個(gè)很好的重載的例子,那就是placement new,它看上去象這樣:?
  void * operator new(size_t, void *location)?
  {?
  return location;?
  }?
  這初看上去有些陌生,但它卻是new操作符的一種常見重載方法,使用一個(gè)額外的變量buffer,當(dāng)new操作符隱含調(diào)用operator new函數(shù)時(shí),把這個(gè)變量傳遞給它。被調(diào)用的operator new函數(shù)除了持有強(qiáng)制的參數(shù)size_t外,還必須接受void*指針參數(shù),指向構(gòu)造對(duì)象占用的內(nèi)存空間。未被使用的(但是強(qiáng)制的)參數(shù)size_t沒(méi)有參數(shù)名字,以防止編譯器警告說(shuō)它未被使用。在使用placement new的情況下,調(diào)用者已經(jīng)獲得了指向內(nèi)存的指針,因?yàn)檎{(diào)用者知道對(duì)象應(yīng)該放在哪里。placement new需要做的就是返回傳遞給它的指針。?
  我們更經(jīng)常使用的new是new操作符(new operator),而非操作符new(operator new),如當(dāng)你使用new操作符構(gòu)建一個(gè)對(duì)象的時(shí)候,實(shí)際上做了兩件事情,一是調(diào)用operator new函數(shù)獲取內(nèi)存,二是調(diào)用對(duì)象的構(gòu)造函數(shù),如:?
  string *ps = new string("Hello, world!");?
  它完成與下面代碼相似的功能:?
  void *memory = operator new(sizeof(string)); // 為String對(duì)象得到未經(jīng)處理的內(nèi)存?
  call string::string("Hello, world!") on *memory; // 調(diào)用構(gòu)造函數(shù)初始化內(nèi)存中的對(duì)象?
  string *ps = static_cast(memory); // ps指針指向新的對(duì)象?
  注意第二步中構(gòu)造函數(shù)的調(diào)用只能由編譯器完成,用戶是不允許這樣操作的,也就是說(shuō)如果你想建立一個(gè)堆對(duì)象就必須用new操作符,不能直接像上面一樣調(diào)用構(gòu)造函數(shù)來(lái)初始化堆對(duì)象。?
  new操作符(new operator)是編譯器內(nèi)置的,其行為被語(yǔ)言固定下來(lái),不受用戶控制。但是它們所調(diào)用的內(nèi)存分配函數(shù)也就是操作符new(operator new)則可以根據(jù)需要進(jìn)行重載。試著回顧new操作符(new operator)與操作符new(operator new)的關(guān)系,如果你想在堆上建立一個(gè)對(duì)象,應(yīng)該用new操作符。它既分配內(nèi)存又為對(duì)象調(diào)用構(gòu)造函數(shù)。如果你僅僅想分配內(nèi)存,就應(yīng)該調(diào)用operator new函數(shù),它不會(huì)調(diào)用構(gòu)造函數(shù)。如果你想定制自己獨(dú)有的內(nèi)存分配過(guò)程,你應(yīng)該重載全局的operator new函數(shù),然后使用new操作符,new操作符會(huì)調(diào)用你定制的operator new。如果你想在一塊已經(jīng)獲得指針的內(nèi)存里建立一個(gè)對(duì)象,應(yīng)該使用placement new。?
  最后需要記住的一點(diǎn)是,delete和new一樣具有以上的特性,只是需要注意的一點(diǎn)是delte操作符中是首先調(diào)用對(duì)象的析構(gòu)函數(shù),然后再調(diào)用operator delete函數(shù)的。?
  2. 針對(duì)數(shù)組的new[]和delete[]操作?
  建立數(shù)組時(shí)new操作符(new[])的行為與單個(gè)對(duì)象建立(new)有少許不同:?
  第一是內(nèi)存不再調(diào)用用operator new函數(shù)進(jìn)行分配,代替以operator new[]函數(shù)(常稱作array new)。它與operator new一樣能被重載,允許定制數(shù)組的內(nèi)存分配,就象定制單個(gè)對(duì)象內(nèi)存分配一樣。?
  第二個(gè)不同是new[]操作時(shí)調(diào)用構(gòu)造函數(shù)的數(shù)量。對(duì)于new[]而言,在數(shù)組里的每一個(gè)對(duì)象的構(gòu)造函數(shù)都必須被調(diào)用。?
  delete[]操作符的語(yǔ)義基本上和new[]相同,他們的實(shí)現(xiàn)類似這樣:?
  void * operator new[](size_t size)?
  {?
  cout <<"new size of array in new[](): "<  int *g =(int *) malloc(sizeof(size));?
  return g;?
  }?
  void operator delete[](void* p)?
  {?
  cout <<"delete address of array pointer in delete[](): "<

  free(p);?
  }?
  3. operator new和delete函數(shù)的實(shí)現(xiàn)?
  operator new實(shí)際上總是以標(biāo)準(zhǔn)的C malloc()完成,雖然并沒(méi)有規(guī)定非得這么做不可。同樣,operator delete也總是以標(biāo)準(zhǔn)得C free()來(lái)實(shí)現(xiàn),不考慮異常處理的話他們類似下面的樣子:?
  extern void* operator new( size_t size )?
  {?
  if( size == 0 )?
  size = 1; // 這里保證像 new T[0] 這樣得語(yǔ)句也是可行的?
  ?
  void *last_alloc;?
  while( !(last_alloc = malloc( size )) )?
  {?
  if( _new_handler )?
  ( *_new_handler )();?
  else?
  return 0;?
  }?
  return last_alloc;?
  }?
  extern void operator delete( void *ptr )?
  {?
  if(ptr) // 從這里可以看出,刪除一個(gè)空指針是安全的?
  free( (char*)ptr );?
  }


==============================================================================================================================

先舉個(gè)例子:

?“operator new”:

[cpp]?view plaincopy
  • class?Foo?{???
  • public:??
  • ?????????void*?operator?new(?size_t?){??
  • ????????cout<<"in?Foo's?operator?new"<<endl;??
  • ????????return?::operator?new(t);??
  • ????}??
  • ????Foo(){??
  • ????????cout<<"in?Foo's?ctor"<<endl;??
  • ????}??
  • };???
  • 注意參數(shù),operator new 的形參類型是 size_t, 此函數(shù)用來(lái)分配內(nèi)存,約等于 malloc,返回的是 void*,并不是 Foo*。確實(shí)與“new operator” 不同。

    "operator new" 其實(shí)是函數(shù)。有全局作用域版,也可以在類內(nèi)覆寫自己的成員函數(shù)版。如果覆寫了成員函數(shù)版,那么在 new something 的時(shí)候全局版就會(huì)被隱藏。


    "new operator" :

    [cpp]?view plaincopy
  • Foo*?foo=new?Foo();??

  • “new operator” ,有人說(shuō)其實(shí)根本沒(méi)有這個(gè)術(shù)語(yǔ)。 c++ 標(biāo)準(zhǔn)里面 new/delete 雖然跟 sizeof之類的同為關(guān)鍵字,并且使用方式也相似,但是new/delete 不能叫做“operator”,而是叫做“expression”,參考討論(http://stackoverflow.com/questions/1885849),,不過(guò)誰(shuí)知道呢,也有人說(shuō)這個(gè)是c++標(biāo)準(zhǔn)里面闡述不明確的地方之一。據(jù)說(shuō) More Effective C++ 用過(guò) “new operator” 這一說(shuō)法 :

    The new operator calls a function to perform the requisite memory allocation, and you can rewrite or overload that function to change its behavior. The name of the function the new operator calls to allocate memory is operator new.

    并且msdn也用了 “new operator” :http://msdn.microsoft.com/en-us/library/kftdy56f%28VS.71%29.aspx

    所以管他呢。。就當(dāng)是有這么個(gè)術(shù)語(yǔ)啦。。

    上面 Foo* foo=new Foo(); 這一句執(zhí)行的時(shí)候,實(shí)際上先后調(diào)用了 “operator new” 和 constructor 兩個(gè)函數(shù)。 operator new 分配了 sizeof(Foo) 大小的內(nèi)存,然后 constructor 做初始化。因此輸出了:

    in Foo's operator new

    in Foo's ctor ?



    ================================================================================================================

    “new”是C++的一個(gè)關(guān)鍵字,同時(shí)也是操作符。關(guān)于new的話題非常多,因?yàn)樗_實(shí)比較復(fù)雜,也非常神秘,下面我將把我了解到的與new有關(guān)的內(nèi)容做一個(gè)總結(jié)。

    new的過(guò)程 當(dāng)我們使用關(guān)鍵字new在堆上動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象時(shí),它實(shí)際上做了三件事:獲得一塊內(nèi)存空間、調(diào)用構(gòu)造函數(shù)、返回正確的指針。當(dāng)然,如果我們創(chuàng)建的是簡(jiǎn)單類型的變量,那么第二步會(huì)被省略。假如我們定義了如下一個(gè)類A: class A
    {
    ?? int i;
    public:
    ?? A(int _i) :i(_i*_i) {}
    ?? void Say() ?{ printf("i=%d/n", i); }
    };
    //調(diào)用new:
    A* pa = new A(3); 那么上述動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象的過(guò)程大致相當(dāng)于以下三句話(只是大致上): A* pa = (A*)malloc(sizeof(A));
    pa->A::A(3);
    return pa; 雖然從效果上看,這三句話也得到了一個(gè)有效的指向堆上的A對(duì)象的指針pa,但區(qū)別在于,當(dāng)malloc失敗時(shí),它不會(huì)調(diào)用分配內(nèi)存失敗處理程序new_handler,而使用new的話會(huì)的。因此我們還是要盡可能的使用new,除非有一些特殊的需求。 new的三種形態(tài) 到目前為止,本文所提到的new都是指的“new operator”或稱為“new expression”,但事實(shí)上在C++中一提到new,至少可能代表以下三種含義:new operator、operator new、placement new。 new operator就是我們平時(shí)所使用的new,其行為就是前面所說(shuō)的三個(gè)步驟,我們不能更改它。但具體到某一步驟中的行為,如果它不滿足我們的具體要求時(shí),我們是有可能更改它的。三個(gè)步驟中最后一步只是簡(jiǎn)單的做一個(gè)指針的類型轉(zhuǎn)換,沒(méi)什么可說(shuō)的,并且在編譯出的代碼中也并不需要這種轉(zhuǎn)換,只是人為的認(rèn)識(shí)罷了。但前兩步就有些內(nèi)容了。 new operator的第一步分配內(nèi)存實(shí)際上是通過(guò)調(diào)用operator new來(lái)完成的,這里的new實(shí)際上是像加減乘除一樣的操作符,因此也是可以重載的。operator new默認(rèn)情況下首先調(diào)用分配內(nèi)存的代碼,嘗試得到一段堆上的空間,如果成功就返回,如果失敗,則轉(zhuǎn)而去調(diào)用一個(gè)new_hander,然后繼續(xù)重復(fù)前面過(guò)程。如果我們對(duì)這個(gè)過(guò)程不滿意,就可以重載operator new,來(lái)設(shè)置我們希望的行為。例如: class A
    {
    public:
    ?? void* operator new(size_t size)
    ?? {
    ?????? printf("operator new called/n");
    ?????? return ::operator new(size);
    ?? }
    };

    A* a = new A(); 這里通過(guò)::operator new調(diào)用了原有的全局的new,實(shí)現(xiàn)了在分配內(nèi)存之前輸出一句話。全局的operator new也是可以重載的,但這樣一來(lái)就不能再遞歸的使用new來(lái)分配內(nèi)存,而只能使用malloc了: void* operator new(size_t size)
    {
    ?? printf("global new/n");
    ???return malloc(size);
    } 相應(yīng)的,delete也有delete operator和operator delete之分,后者也是可以重載的。并且,如果重載了operator new,就應(yīng)該也相應(yīng)的重載operator delete,這是良好的編程習(xí)慣。 new的第三種形態(tài)——placement new是用來(lái)實(shí)現(xiàn)定位構(gòu)造的,因此可以實(shí)現(xiàn)new operator三步操作中的第二步,也就是在取得了一塊可以容納指定類型對(duì)象的內(nèi)存后,在這塊內(nèi)存上構(gòu)造一個(gè)對(duì)象,這有點(diǎn)類似于前面代碼中的“p->A::A(3);”這句話,但這并不是一個(gè)標(biāo)準(zhǔn)的寫法,正確的寫法是使用placement new: #include <new.h>

    void main()
    {
    ?? char s[sizeof(A)];
    ?? A* p = (A*)s;
    ?? new(p) A(3); //p->A::A(3);
    ?? p->Say();
    } 對(duì)頭文件<new>或<new.h>的引用是必須的,這樣才可以使用placement new。這里“new(p) A(3)”這種奇怪的寫法便是placement new了,它實(shí)現(xiàn)了在指定內(nèi)存地址上用指定類型的構(gòu)造函數(shù)來(lái)構(gòu)造一個(gè)對(duì)象的功能,后面A(3)就是對(duì)構(gòu)造函數(shù)的顯式調(diào)用。這里不難發(fā)現(xiàn),這塊指定的地址既可以是棧,又可以是堆,placement對(duì)此不加區(qū)分。但是,除非特別必要,不要直接使用placement new ,這畢竟不是用來(lái)構(gòu)造對(duì)象的正式寫法,只不過(guò)是new operator的一個(gè)步驟而已。使用new operator地編譯器會(huì)自動(dòng)生成對(duì)placement new的調(diào)用的代碼,因此也會(huì)相應(yīng)的生成使用delete時(shí)調(diào)用析構(gòu)函數(shù)的代碼。如果是像上面那樣在棧上使用了placement new,則必須手工調(diào)用析構(gòu)函數(shù),這也是顯式調(diào)用析構(gòu)函數(shù)的唯一情況: p->~A(); 當(dāng)我們覺得默認(rèn)的new operator對(duì)內(nèi)存的管理不能滿足我們的需要,而希望自己手工的管理內(nèi)存時(shí),placement new就有用了。STL中的allocator就使用了這種方式,借助placement new來(lái)實(shí)現(xiàn)更靈活有效的內(nèi)存管理。 處理內(nèi)存分配異常 正如前面所說(shuō),operator new的默認(rèn)行為是請(qǐng)求分配內(nèi)存,如果成功則返回此內(nèi)存地址,如果失敗則調(diào)用一個(gè)new_handler,然后再重復(fù)此過(guò)程。于是,想要從operator new的執(zhí)行過(guò)程中返回,則必然需要滿足下列條件之一: l?????????分配內(nèi)存成功 l?????????new_handler中拋出bad_alloc異常 l?????????new_handler中調(diào)用exit()或類似的函數(shù),使程序結(jié)束 于是,我們可以假設(shè)默認(rèn)情況下operator new的行為是這樣的: void* operator new(size_t size)
    {
    ?? void* p = null
    ?? while(!(p = malloc(size)))
    ?? {
    ?????? if(null == new_handler)
    ????????? throw bad_alloc();
    ?????? try
    ?????? {
    ????????? new_handler();
    ?????? }
    ?????? catch(bad_alloc e)
    ?????? {
    ????????? throw e;
    ?????? }
    ?????? catch(…)
    ?????? {}
    ?? }
    ?? return p;
    } 在默認(rèn)情況下,new_handler的行為是拋出一個(gè)bad_alloc異常,因此上述循環(huán)只會(huì)執(zhí)行一次。但如果我們不希望使用默認(rèn)行為,可以自定義一個(gè)new_handler,并使用std::set_new_handler函數(shù)使其生效。在自定義的new_handler中,我們可以拋出異常,可以結(jié)束程序,也可以運(yùn)行一些代碼使得有可能有內(nèi)存被空閑出來(lái),從而下一次分配時(shí)也許會(huì)成功,也可以通過(guò)set_new_handler來(lái)安裝另一個(gè)可能更有效的new_handler。例如: void MyNewHandler()
    {
    ?? printf(“New handler called!/n”);
    ?? throw std::bad_alloc();
    }

    std::set_new_handler(MyNewHandler); 這里new_handler程序在拋出異常之前會(huì)輸出一句話。應(yīng)該注意,在new_handler的代碼里應(yīng)該注意避免再嵌套有對(duì)new的調(diào)用,因?yàn)槿绻@里調(diào)用new再失敗的話,可能會(huì)再導(dǎo)致對(duì)new_handler的調(diào)用,從而導(dǎo)致無(wú)限遞歸調(diào)用。——這是我猜的,并沒(méi)有嘗試過(guò)。 在編程時(shí)我們應(yīng)該注意到對(duì)new的調(diào)用是有可能有異常被拋出的,因此在new的代碼周圍應(yīng)該注意保持其事務(wù)性,即不能因?yàn)檎{(diào)用new失敗拋出異常來(lái)導(dǎo)致不正確的程序邏輯或數(shù)據(jù)結(jié)構(gòu)的出現(xiàn)。例如: class SomeClass
    {
    ?? static int count;
    ?? SomeClass() {}
    public:
    ?? static SomeClass* GetNewInstance()
    ?? {
    ?????? count++;
    ?????? return new SomeClass();
    ?? }
    }; 靜態(tài)變量count用于記錄此類型生成的實(shí)例的個(gè)數(shù),在上述代碼中,如果因new分配內(nèi)存失敗而拋出異常,那么其實(shí)例個(gè)數(shù)并沒(méi)有增加,但count變量的值卻已經(jīng)多了一個(gè),從而數(shù)據(jù)結(jié)構(gòu)被破壞。正確的寫法是: static SomeClass* GetNewInstance()
    {
    ?? SomeClass* p = new SomeClass();
    ?? count++;
    ?? return p;
    } 這樣一來(lái),如果new失敗則直接拋出異常,count的值不會(huì)增加。類似的,在處理線程同步時(shí),也要注意類似的問(wèn)題: void SomeFunc()
    {
    ?? lock(someMutex); //加一個(gè)鎖
    ?? delete p;
    ?? p = new SomeClass();
    ?? unlock(someMutex);
    } 此時(shí),如果new失敗,unlock將不會(huì)被執(zhí)行,于是不僅造成了一個(gè)指向不正確地址的指針p的存在,還將導(dǎo)致someMutex永遠(yuǎn)不會(huì)被解鎖。這種情況是要注意避免的。(參考:C++箴言:爭(zhēng)取異常安全的代碼) STL的內(nèi)存分配與traits技巧 在《STL原碼剖析》一書中詳細(xì)分析了SGI STL的內(nèi)存分配器的行為。與直接使用new operator不同的是,SGI STL并不依賴C++默認(rèn)的內(nèi)存分配方式,而是使用一套自行實(shí)現(xiàn)的方案。首先SGI STL將可用內(nèi)存整塊的分配,使之成為當(dāng)前進(jìn)程可用的內(nèi)存,當(dāng)程序中確實(shí)需要分配內(nèi)存時(shí),先從這些已請(qǐng)求好的大內(nèi)存塊中嘗試取得內(nèi)存,如果失敗的話再嘗試整塊的分配大內(nèi)存。這種做法有效的避免了大量?jī)?nèi)存碎片的出現(xiàn),提高了內(nèi)存管理效率。 為了實(shí)現(xiàn)這種方式,STL使用了placement new,通過(guò)在自己管理的內(nèi)存空間上使用placement new來(lái)構(gòu)造對(duì)象,以達(dá)到原有new operator所具有的功能。 template <class T1, class T2>
    inline void construct(T1* p, const T2& value)
    {
    ?? new(p) T1(value);
    } 此函數(shù)接收一個(gè)已構(gòu)造的對(duì)象,通過(guò)拷貝構(gòu)造的方式在給定的內(nèi)存地址p上構(gòu)造一個(gè)新對(duì)象,代碼中后半截T1(value)便是placement new語(yǔ)法中調(diào)用構(gòu)造函數(shù)的寫法,如果傳入的對(duì)象value正是所要求的類型T1,那么這里就相當(dāng)于調(diào)用拷貝構(gòu)造函數(shù)。類似的,因使用了placement new,編譯器不會(huì)自動(dòng)產(chǎn)生調(diào)用析構(gòu)函數(shù)的代碼,需要手工的實(shí)現(xiàn): template <class T>
    inline void destory(T* pointer)
    {
    ?? pointer->~T();
    } 與此同時(shí),STL中還有一個(gè)接收兩個(gè)迭代器的destory版本,可將某容器上指定范圍內(nèi)的對(duì)象全部銷毀。典型的實(shí)現(xiàn)方式就是通過(guò)一個(gè)循環(huán)來(lái)對(duì)此范圍內(nèi)的對(duì)象逐一調(diào)用析構(gòu)函數(shù)。如果所傳入的對(duì)象是非簡(jiǎn)單類型,這樣做是必要的,但如果傳入的是簡(jiǎn)單類型,或者根本沒(méi)有必要調(diào)用析構(gòu)函數(shù)的自定義類型(例如只包含數(shù)個(gè)int成員的結(jié)構(gòu)體),那么再逐一調(diào)用析構(gòu)函數(shù)是沒(méi)有必要的,也浪費(fèi)了時(shí)間。為此,STL使用了一種稱為“type traits”的技巧,在編譯器就判斷出所傳入的類型是否需要調(diào)用析構(gòu)函數(shù): template <class ForwardIterator>
    inline void destory(ForwardIterator first, ForwardIterator last)
    {
    ?? __destory(first, last, value_type(first));
    } 其中value_type()用于取出迭代器所指向的對(duì)象的類型信息,于是: template<class ForwardIterator, class T>
    inline void __destory(ForwardIterator first, ForwardIterator last, T*)
    {
    ?? typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
    ?? __destory_aux(first, last, trivial_destructor());
    }
    //如果需要調(diào)用析構(gòu)函數(shù):
    template<class ForwardIterator>
    inline void __destory_aux(ForwardIterator first, ForwardIterator last, __false_type)
    {
    ?? for(; first < last; ++first)
    ?????? destory(&*first); //因first是迭代器,*first取出其真正內(nèi)容,然后再用&取地址
    }
    //如果不需要,就什么也不做:
    tempalte<class ForwardIterator>
    inline void __destory_aux(ForwardIterator first, ForwardIterator last, __true_type)
    {} 因上述函數(shù)全都是inline的,所以多層的函數(shù)調(diào)用并不會(huì)對(duì)性能造成影響,最終編譯的結(jié)果根據(jù)具體的類型就只是一個(gè)for循環(huán)或者什么都沒(méi)有。這里的關(guān)鍵在于__type_traits<T>這個(gè)模板類上,它根據(jù)不同的T類型定義出不同的has_trivial_destructor的結(jié)果,如果T是簡(jiǎn)單類型,就定義為__true_type類型,否則就定義為__false_type類型。其中__true_type、__false_type只不過(guò)是兩個(gè)沒(méi)有任何內(nèi)容的類,對(duì)程序的執(zhí)行結(jié)果沒(méi)有什么意義,但在編譯器看來(lái)它對(duì)模板如何特化就具有非常重要的指導(dǎo)意義了,正如上面代碼所示的那樣。__type_traits<T>也是特化了的一系列模板類: struct __true_type {};
    struct __false_type {};
    template <class T>
    struct __type_traits
    {
    public:
    ?? typedef __false _type has_trivial_destructor;
    ???……
    };
    template<>?//模板特化
    struct __type_traits<int>??? //int的特化版本
    {
    public:
    ?? typedef __true_type has_trivial_destructor;
    ???……
    };
    …… //其他簡(jiǎn)單類型的特化版本 如果要把一個(gè)自定義的類型MyClass也定義為不調(diào)用析構(gòu)函數(shù),只需要相應(yīng)的定義__type_traits<T>的一個(gè)特化版本即可: template<>
    struct __type_traits<MyClass>
    {
    public:
    ?? typedef __true_type has_trivial_destructor;
    ???……
    }; 模板是比較高級(jí)的C++編程技巧,模板特化、模板偏特化就更是技巧性很強(qiáng)的東西,STL中的type_traits充分借助模板特化的功能,實(shí)現(xiàn)了在程序編譯期通過(guò)編譯器來(lái)決定為每一處調(diào)用使用哪個(gè)特化版本,于是在不增加編程復(fù)雜性的前提下大大提高了程序的運(yùn)行效率。更詳細(xì)的內(nèi)容可參考《STL源碼剖析》第二、三章中的相關(guān)內(nèi)容。 帶有“[]”的new和delete 我們經(jīng)常會(huì)通過(guò)new來(lái)動(dòng)態(tài)創(chuàng)建一個(gè)數(shù)組,例如: char* s = new char[100];
    ……
    delete s; 嚴(yán)格的說(shuō),上述代碼是不正確的,因?yàn)槲覀冊(cè)诜峙鋬?nèi)存時(shí)使用的是new[],而并不是簡(jiǎn)單的new,但釋放內(nèi)存時(shí)卻用的是delete。正確的寫法是使用delete[]: delete[] s; 但是,上述錯(cuò)誤的代碼似乎也能編譯執(zhí)行,并不會(huì)帶來(lái)什么錯(cuò)誤。事實(shí)上,new與new[]、delete與delete[]是有區(qū)別的,特別是當(dāng)用來(lái)操作復(fù)雜類型時(shí)。假如針對(duì)一個(gè)我們自定義的類MyClass使用new[]: MyClass* p = new MyClass[10]; 上述代碼的結(jié)果是在堆上分配了10個(gè)連續(xù)的MyClass實(shí)例,并且已經(jīng)對(duì)它們依次調(diào)用了構(gòu)造函數(shù),于是我們得到了10個(gè)可用的對(duì)象,這一點(diǎn)與Java、C#有區(qū)別的,Java、C#中這樣的結(jié)果只是得到了10個(gè)null。換句話說(shuō),使用這種寫法時(shí)MyClass必須擁有不帶參數(shù)的構(gòu)造函數(shù),否則會(huì)發(fā)現(xiàn)編譯期錯(cuò)誤,因?yàn)榫幾g器無(wú)法調(diào)用有參數(shù)的構(gòu)造函數(shù)。 當(dāng)這樣構(gòu)造成功后,我們可以再將其釋放,釋放時(shí)使用delete[]: delete[] p; 當(dāng)我們對(duì)動(dòng)態(tài)分配的數(shù)組調(diào)用delete[]時(shí),其行為根據(jù)所申請(qǐng)的變量類型會(huì)有所不同。如果p指向簡(jiǎn)單類型,如int、char等,其結(jié)果只不過(guò)是這塊內(nèi)存被回收,此時(shí)使用delete[]與delete沒(méi)有區(qū)別,但如果p指向的是復(fù)雜類型,delete[]會(huì)針對(duì)動(dòng)態(tài)分配得到的每個(gè)對(duì)象調(diào)用析構(gòu)函數(shù),然后再釋放內(nèi)存。因此,如果我們對(duì)上述分配得到的p指針直接使用delete來(lái)回收,雖然編譯期不報(bào)什么錯(cuò)誤(因?yàn)榫幾g器根本看不出來(lái)這個(gè)指針p是如何分配的),但在運(yùn)行時(shí)(DEBUG情況下)會(huì)給出一個(gè)Debug assertion failed提示。 到這里,我們很容易提出一個(gè)問(wèn)題——delete[]是如何知道要為多少個(gè)對(duì)象調(diào)用析構(gòu)函數(shù)的?要回答這個(gè)問(wèn)題,我們可以首先看一看new[]的重載。 class MyClass
    {
    ???int a;
    public:
    ?? MyClass() { printf("ctor/n"); }
    ?? ~MyClass() { printf("dtor/n"); }
    };

    void* operator new[](size_t size)
    {
    ???void* p = operator new(size);
    ?? printf("calling new[] with size=%d address=%p/n", size, p);
    ???return p;
    }

    // 主函數(shù)
    MyClass* mc = new MyClass[3];
    printf("address of mc=%p/n", mc);
    delete[] mc; 運(yùn)行此段代碼,得到的結(jié)果為:(VC2005) calling new[] with size=16?address=003A5A58 ctor ctor ctor address of mc=003A5A5C dtor dtor dtor 雖然對(duì)構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用結(jié)果都在預(yù)料之中,但所申請(qǐng)的內(nèi)存空間大小以及地址的數(shù)值卻出現(xiàn)了問(wèn)題。我們的類MyClass的大小顯然是4個(gè)字節(jié),并且申請(qǐng)的數(shù)組中有3個(gè)元素,那么應(yīng)該一共申請(qǐng)12個(gè)字節(jié)才對(duì),但事實(shí)上系統(tǒng)卻為我們申請(qǐng)了16字節(jié),并且在operator new[]返后我們得到的內(nèi)存地址是實(shí)際申請(qǐng)得到的內(nèi)存地址值加4的結(jié)果。也就是說(shuō),當(dāng)為復(fù)雜類型動(dòng)態(tài)分配數(shù)組時(shí),系統(tǒng)自動(dòng)在最終得到的內(nèi)存地址前空出了4個(gè)字節(jié),我們有理由相信這4個(gè)字節(jié)的內(nèi)容與動(dòng)態(tài)分配數(shù)組的長(zhǎng)度有關(guān)。通過(guò)單步跟蹤,很容易發(fā)現(xiàn)這4個(gè)字節(jié)對(duì)應(yīng)的int值為0x00000003,也就是說(shuō)記錄的是我們分配的對(duì)象的個(gè)數(shù)。改變一下分配的個(gè)數(shù)然后再次觀察的結(jié)果證實(shí)了我的想法。于是,我們也有理由認(rèn)為new[] operator的行為相當(dāng)于下面的偽代碼: template <class T>
    T* New[](int count)
    {
    ?? int size = sizeof(T) * count + 4;
    ?? void* p = T::operator new[](size);
    ?? *(int*)p = count;
    ?? T* pt = (T*)((int)p + 4);
    ?? for(int i = 0; i < count; i++)
    ?????? new(&pt[i]) T();
    ?? return pt;
    } 上述示意性的代碼省略了異常處理的部分,只是展示當(dāng)我們對(duì)一個(gè)復(fù)雜類型使用new[]來(lái)動(dòng)態(tài)分配數(shù)組時(shí)其真正的行為是什么,從中可以看到它分配了比預(yù)期多4個(gè)字節(jié)的內(nèi)存并用它來(lái)保存對(duì)象的個(gè)數(shù),然后對(duì)于后面每一塊空間使用placement new來(lái)調(diào)用無(wú)參構(gòu)造函數(shù),這也就解釋了為什么這種情況下類必須有無(wú)參構(gòu)造函數(shù),最后再將首地址返回。類似的,我們很容易寫出相應(yīng)的delete[]的實(shí)現(xiàn)代碼: template <class T>
    void Delete[](T* pt)
    {
    ?? int count = ((int*)pt)[-1];
    ?? for(int i = 0; i < count; i++)
    ?????? pt[i].~T();
    ?? void* p = (void*)((int)pt – 4);
    ?? T::operator delete[](p);
    } 由此可見,在默認(rèn)情況下operator new[]與operator new的行為是相同的,operator delete[]與operator delete也是,不同的是new operator與new[] operator、delete operator與delete[] operator。當(dāng)然,我們可以根據(jù)不同的需要來(lái)選擇重載帶有和不帶有“[]”的operator new和delete,以滿足不同的具體需求。 把前面類MyClass的代碼稍做修改——注釋掉析構(gòu)函數(shù),然后再來(lái)看看程序的輸出: calling new[] with size=12 address=003A5A58 ctor ctor ctor address of mc=003A5A58 這一次,new[]老老實(shí)實(shí)的申請(qǐng)了12個(gè)字節(jié)的內(nèi)存,并且申請(qǐng)的結(jié)果與new[] operator返回的結(jié)果也是相同的,看來(lái),是否在前面添加4個(gè)字節(jié),只取決于這個(gè)類有沒(méi)有析構(gòu)函數(shù),當(dāng)然,這么說(shuō)并不確切,正確的說(shuō)法是這個(gè)類是否需要調(diào)用構(gòu)造函數(shù),因?yàn)槿缦聝煞N情況下雖然這個(gè)類沒(méi)聲明析構(gòu)函數(shù),但還是多申請(qǐng)了4個(gè)字節(jié):一是這個(gè)類中擁有需要調(diào)用析構(gòu)函數(shù)的成員,二是這個(gè)類繼承自需要調(diào)用析構(gòu)函數(shù)的類。于是,我們可以遞歸的定義“需要調(diào)用析構(gòu)函數(shù)的類”為以下三種情況之一: 1 顯式的聲明了析構(gòu)函數(shù)的 2 擁有需要調(diào)用析構(gòu)函數(shù)的類的成員的 3 繼承自需要調(diào)用析構(gòu)函數(shù)的類的 類似的,動(dòng)態(tài)申請(qǐng)簡(jiǎn)單類型的數(shù)組時(shí),也不會(huì)多申請(qǐng)4個(gè)字節(jié)。于是在這兩種情況下,釋放內(nèi)存時(shí)使用delete或delete[]都可以,但為養(yǎng)成良好的習(xí)慣,我們還是應(yīng)該注意只要是動(dòng)態(tài)分配的數(shù)組,釋放時(shí)就使用delete[]。 釋放內(nèi)存時(shí)如何知道長(zhǎng)度 但這同時(shí)又帶來(lái)了新問(wèn)題,既然申請(qǐng)無(wú)需調(diào)用析構(gòu)函數(shù)的類或簡(jiǎn)單類型的數(shù)組時(shí)并沒(méi)有記錄個(gè)數(shù)信息,那么operator delete,或更直接的說(shuō)free()是如何來(lái)回收這塊內(nèi)存的呢?這就要研究malloc()返回的內(nèi)存的結(jié)構(gòu)了。與new[]類似的是,實(shí)際上在malloc()申請(qǐng)內(nèi)存時(shí)也多申請(qǐng)了數(shù)個(gè)字節(jié)的內(nèi)容,只不過(guò)這與所申請(qǐng)的變量的類型沒(méi)有任何關(guān)系,我們從調(diào)用malloc時(shí)所傳入的參數(shù)也可以理解這一點(diǎn)——它只接收了要申請(qǐng)的內(nèi)存的長(zhǎng)度,并不關(guān)系這塊內(nèi)存用來(lái)保存什么類型。下面運(yùn)行這樣一段代碼做個(gè)實(shí)驗(yàn): char *p = 0;
    for(int i = 0; i < 40; i += 4)
    {
    ???char* s = new char[i];
    ?? printf("alloc %2d bytes, address=%p distance=%d/n", i, s, s - p);
    ?? p = s;
    } 我們直接來(lái)看VC2005下Release版本的運(yùn)行結(jié)果,DEBUG版因包含了較多的調(diào)試信息,這里就不分析了: alloc?0 bytes, address=003A36F0 distance=3815152 alloc?4 bytes, address=003A3700 distance=16 alloc?8 bytes, address=003A3710 distance=16 alloc 12 bytes, address=003A3720 distance=16 alloc 16 bytes, address=003A3738 distance=24 alloc 20 bytes, address=003A84C0 distance=19848 alloc 24 bytes, address=003A84E0 distance=32 alloc 28 bytes, address=003A8500 distance=32 alloc 32 bytes, address=003A8528 distance=40 alloc 36 bytes, address=003A8550 distance=40 每一次分配的字節(jié)數(shù)都比上一次多4,distance值記錄著與上一次分配的差值,第一個(gè)差值沒(méi)有實(shí)際意義,中間有一個(gè)較大的差值,可能是這塊內(nèi)存已經(jīng)被分配了,于是也忽略它。結(jié)果中最小的差值為16字節(jié),直到我們申請(qǐng)16字節(jié)時(shí),這個(gè)差值變成了24,后面也有類似的規(guī)律,那么我們可以認(rèn)為申請(qǐng)所得的內(nèi)存結(jié)構(gòu)是如下這樣的: 從圖中不難看出,當(dāng)我們要分配一段內(nèi)存時(shí),所得的內(nèi)存地址和上一次的尾地址至少要相距8個(gè)字節(jié)(在DEBUG版中還要更多),那么我們可以猜想,這8個(gè)字節(jié)中應(yīng)該記錄著與這段所分配的內(nèi)存有關(guān)的信息。觀察這8個(gè)節(jié)內(nèi)的內(nèi)容,得到結(jié)果如下: 圖中右邊為每次分配所得的地址之前8個(gè)字節(jié)的內(nèi)容的16進(jìn)制表示,從圖中紅線所表示可以看到,這8個(gè)字節(jié)中的第一個(gè)字節(jié)乘以8即得到相臨兩次分配時(shí)的距離,經(jīng)過(guò)試驗(yàn)一次性分配更大的長(zhǎng)度可知,第二個(gè)字節(jié)也是這個(gè)意義,并且代表高8位,也就說(shuō)前面空的這8個(gè)字節(jié)中的前兩個(gè)字節(jié)記錄了一次分配內(nèi)存的長(zhǎng)度信息,后面的六個(gè)字節(jié)可能與空閑內(nèi)存鏈表的信息有關(guān),在翻譯內(nèi)存時(shí)用來(lái)提供必要的信息。這就解答了前面提出的問(wèn)題,原來(lái)C/C++在分配內(nèi)存時(shí)已經(jīng)記錄了足夠充分的信息用于回收內(nèi)存,只不過(guò)我們平常不關(guān)心它罷了。 ====================================================================================================== 1. plain new 普通new
    [cpp]?view plaincopy
  • void*operator?new(std::size_t)throw(std::bad_alloc);?????
  • void?operator?delete(?void?*)?throw();???
  • 該運(yùn)算符在分配失敗時(shí)將拋出異常,而非返回NULL。使用時(shí)要包含 <new>頭文件。正常使用new,但要配以異常處理。如:
    [cpp]?view plaincopy
  • char?*getMemory(unsigned?long?size)?????
  • {????char?*?p?=?new?char[size];?????
  • ??????return?p;?}?????
  • void?main(void?)?????
  • {????try{?????
  • ????????char?*?p?=?getMemory(1000000);//可能發(fā)生異常?????
  • ????????//?...?????
  • ????????delete?[?]?p;?????
  • ????????}?????
  • ????catch(const?std::bad_alloc?&?ex)?????
  • ????{??cout?<?<ex.what();????}?????
  • }??
  • 2.nothrow new ?不拋擲異常new
    [cpp]?view plaincopy
  • void*operator?new(std::size_t,const?std::nothrow_t?&?)throw();?????
  • void?operator?delete(?void?*)?throw();??
  • 該運(yùn)算符在分配失敗時(shí)不拋出異常,而是返回NULL。使用時(shí)要包含 <new>頭文件。?
    該函數(shù)的第2形參是 struct nothrow_t { ?};它是個(gè)全局常對(duì)象 const nothrow_t nothrow; 用來(lái)作為 new 運(yùn)算符的標(biāo)志,以區(qū)別前一個(gè)new.
    3.placement new 放置new
    [cpp]?view plaincopy
  • >void*operator?new(std::size_t?,void?*);?????
  • void?operator?delete(?void?*?,void?*);???
  • 該運(yùn)算符是在已分配的內(nèi)存上重新構(gòu)造對(duì)象,因?yàn)椴环峙鋬?nèi)存,所以不必?fù)?dān)心分配失敗。唯一的工作是調(diào)用構(gòu)造函數(shù)。要包含 <new>頭文件。
    [cpp]?view plaincopy
  • #?include?<new>?????
  • #?include?<iostream>?????
  • void?main()?????
  • {??using?namespace?std;?????
  • ????char?*?p?=?new(nothrow)?char?[4];?????
  • ????if?(p?==?NULL)?????
  • ????{??cout?<?<“allocte?failed”?<?<endl;??exit(?-1?);????}?????
  • ????//?...?????
  • ????long?*?q?=?new(p)long(1000);?????
  • ????delete?[?]p;????//只釋放?p,不要用q釋放。?????
  • }???
  • ?p和q僅僅是首址相同,所構(gòu)建的對(duì)象可以類型不同。所“放置”的空間應(yīng)小于原空間,以防不測(cè)。當(dāng)”放置new”超過(guò)了申請(qǐng)的范圍,Debug版下會(huì)掛機(jī),但Release版竟然能運(yùn)行而不出錯(cuò)!
    該運(yùn)算符的作用是:只要第一次分配成功,不再擔(dān)心分配失敗。
    [cpp]?view plaincopy
  • #?include?<new>?????
  • #?include?<iostream>?????
  • void?main()?????
  • {??using?namespace?std;?????
  • ????char?*?p?=?new(nothrow)?char?[100];?????
  • ????if?(p?==?NULL)?????
  • ????{??cout?<?<“allocte?failed”?<?<endl;??exit(?-1?);????}?????
  • ????long?*?q1?=?new(p)long(100);?????
  • ????//?使用q1??...?????
  • ????int?*?q2?=?new(p)?int[100/sizeof(int)?];?????
  • ????//?使用q2?...?????
  • ????ADT?*?q3?=?new(p)?ADT[100/sizeof(ADT)?];?????
  • ????//?使用q3??然后釋放對(duì)象?...?????
  • ????delete?[?]p;????//只釋放空間,不再析構(gòu)對(duì)象。?????
  • }??
  • 注意:使用該運(yùn)算符構(gòu)造的對(duì)象或數(shù)組,一定要顯式調(diào)用析構(gòu)函數(shù),不可用delete代替析構(gòu),因?yàn)閜lacement new 的對(duì)象的大小不再與原空間相同。 [cpp]?view plaincopy
  • #?include?<new>?????
  • #?include?<iostream>?????
  • void?main()?????
  • {??using?namespace?std;?????
  • ????char?*?p?=?new(nothrow)?char?[sizeof(ADT)+2];?????
  • ????if?(p?==?NULL)?????
  • ????{??cout?<?<“allocte?failed”?<?<endl;??exit(?-1?);????}?????
  • ????//?...?????
  • ????ADT?*?q?=?new(p)?ADT;?????
  • ????//?...?????
  • ????//?delete?q;????//?錯(cuò)誤?????
  • ????q->?ADT::~ADT();?//顯式調(diào)用析構(gòu)函數(shù),僅釋放對(duì)象?????
  • ????delete?[?]p;????//最后,再用原指針來(lái)釋放內(nèi)存.?????
  • }???
  • ?placement new 的主要用途就是可以反復(fù)使用一塊已申請(qǐng)成功的內(nèi)存空間。這樣可以避免申請(qǐng)失敗的徒勞,又可以避免使用后的釋放。?

    ? ? 特別要注意的是對(duì)于 placement new 絕不可以調(diào)用的delete, 因?yàn)樵搉ew只是使用別人替它申請(qǐng)的地方(只是個(gè)租房戶,不是房主。無(wú)權(quán)將房子賣掉)。釋放內(nèi)存是nothrow new的事,即要使用原來(lái)的指針釋放內(nèi)存


    總結(jié)

    以上是生活随笔為你收集整理的深入C++的new的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。