1. 程式人生 > 程式設計 >淺析C++ new的三種面貌

淺析C++ new的三種面貌

1.new的三種面貌

C++中使用new運算子產生一個存在於Heap(堆)上物件時,實際上呼叫了operator new()函式和placement new()函式。在使用new建立堆物件時,我們要清楚認清楚new的三種面貌,分別是:new operator、operator new()和placement new()。

1.1new operator

new operator是C++保留的關鍵字,我們無法改變其含義,但我們可以改變new完成它功能時呼叫的兩個函式,operator new()和placement new()。也就是說我們在使用運算子new時,其最終是通過呼叫operator new()和placement new()來完成堆物件的建立工作。使用new operator時,其完成的工作有如下三步:

淺析C++ new的三種面貌

因此,當我們經常按照如下方式使用new operator時:

string* sp=new string(“hello world”);

實際上等價於:

//第一步:申請原始空間,行為類似於malloc
void* raw=operator new(strlen(“hello world”));
//第二步:通過placement new呼叫string類的建構函式,初始化申請空間
new (raw) string(“hello world”);
//第三部:返回物件指標
string* sp=static_cast<string*>(raw);

1.2operator new()

operator new()用於申請Heap空間,功能類似於C語言的庫函式malloc(),嘗試從堆上獲取一段記憶體空間,如果成功則直接返回,如果失敗則轉而去呼叫一個new handler,然後丟擲一個bad_alloc異常。operator new()的函式原型一般為

void* operator new (std::size_t size) throw (std::bad_alloc);

具體實現如下:

void *__CRTDECL operator new(size_t size) throw (std::bad_alloc)
{    
  // try to allocate size bytes
  void *p;
  while ((p = malloc(size)) == 0)   //申請空間
    if (_callnewh(size) == 0)    //若申請失敗則呼叫處理函式
    {    
      // report no memory
      static const std::bad_alloc nomem;
      _RAISE(nomem);        //#define _RAISE(x) ::std:: _Throw(x) 丟擲nomem的異常
    }
  return (p);
}

注意:

(1)函式後新增throw表示可能會丟擲throw後括號內的異常;
(2)operator new()分為全域性和類成員。當為類成員函式時,使用new產生類物件時呼叫的則是其成員函式operator new()。如果要過載全域性的operator new會改變所有預設的operator new的方式,所以必須要注意。正如new與delete相互對應,operator new與operator delete也是一一對應,如果過載了operator new,那麼理應過載operator delete。

placement new():

一般來說,使用new申請空間時,是從系統的堆中分配空間,申請所得空間的位置根據當時記憶體實際使用情況決定。但是,在某些特殊情況下,可能需要在程式設計師指定的特定記憶體建立物件,這就是所謂的“定位放置new”(placement new)操作。placement new()是一個特殊的operator new(),因為其是operator new()函式的過載版本,只是取了個別名叫作placement new罷了。作用是在已經獲得的堆空間上呼叫類建構函式來初始化物件,也就是定位構造物件。通常情況下,建構函式是由編譯器自動呼叫的,但是不排除程式設計師手動呼叫的可能性,比如對一塊未初始化的記憶體進行處理,獲得想要的物件,這是需要求助於placement new()。placement new()是C++標準庫的一部分,被申明在標頭檔案<new>中,其函式原型是:

void* operator new(std::size_t,void* __p);

具體實現如下:

void* operator new(std::size_t,void* __p) throw()
{
  return __p;
}

注意:

(1)placement new()的函式原型不是void* placement new(std::size_t,void* __p) ;
(2)placement new只是operator new()的一個過載,多了一個已經申請好的空間,由void* __p指定;
(3)用法是new (addr) constructor(),對addr指定的記憶體空間呼叫建構函式進行初始化。為何稱為placement new,從其用法可以看出只是用於呼叫建構函式。

定位放置new操作的語法形式不同於普通的new操作。例如,一般都用如下語句A* p=new A;申請空間,而定位放置new操作則使用如下語句A* p=new (ptr) A;申請空間,其中ptr就是程式設計師指定的記憶體首地址。考察如下程式。

#include <iostream>
using namespace std;

class A{
  int num;
public:
  A(){
    cout<<"A's constructor"<<endl;
  }

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

int main(){
  char mem[100];
  mem[0]='A';
  mem[1]='\0';
  mem[2]='\0';
  mem[3]='\0';
  cout<<(void*)mem<<endl;
  A* p=new (mem) A;
  cout<<p<<endl;
  p->show();
  p->~A();
  getchar();
}

程式執行結果:

0024F924
A's constructor
0024F924
num:65
~A

閱讀以上程式,注意以下幾點。

(1)用定位放置new操作,既可以在棧(stack)上生成物件,也可以在堆(heap)上生成物件。如本例就是在棧上生成一個物件。

(2)使用語句A* p=new (mem) A;定位生成物件時,指標p和陣列名mem指向同一片儲存區。所以,與其說定位放置new操作是申請空間,還不如說是利用已經請好的空間,真正的申請空間的工作是在此之前完成的。

(3)使用語句A *p=new (mem) A;定位生成物件是,會自動呼叫類A的建構函式,但是由於物件的空間不會自動釋放(物件實際上是借用別人的空間),所以必須顯示的呼叫類的解構函式,如本例中的p->~A()。

(4)萬不得已才使用placement new,只有當你真的在意物件在記憶體中的特定位置時才使用它。例如,你的硬體有一個記憶體映像的I/O記時器裝置,並且你想放置一個Clock物件在哪那個位置。

總結:

(1)若想在堆上建立一個物件,應該用new操作符。它既分配記憶體又呼叫其建構函式進行初始化。
(2)若僅僅想分配記憶體,應該呼叫operator new(),他不會呼叫建構函式。若想定製自己在堆物件被建立時的記憶體分配過程,應該重寫自己的operator new()。
(3)若想在一塊已經獲得的記憶體空間上建立一個物件,應該用placement new。雖然在實際開發過程中,很少需要重寫operator new(),使用內建的operator new()即可完成大部分程式所需的功能。但知道這些,有助於一個C++程式猿對C++記憶體的管理有個清楚的認識。

2.瞭解delete和operator delete()

為了避免記憶體洩漏,每個動態記憶體分配必須與一個等同相反的 deallocation 對應。數operator delete與delete操作符的關係與operator new與new操作符是一樣的。delete用於使用使用new申請的空間,operator delete用於釋放operator new申請的空間(類似於malloc與free),那誰來清理placement new初始化的記憶體內容呢?唯一辦法就是呼叫物件的解構函式。

示例程式碼:

string* sp=new string(“hello world”);
delete sp;

第一行程式碼在上文已經剖析,那麼當呼叫delete sp時,發生了什麼?

delete sp等價於:

ps->~string(); //用於清理記憶體內容,對應placement new
operator delete(ps);//釋放記憶體空間,對應於operator new()

其中operator delete()的函式原型為:

void operator delete(void *memoryToBeDeallocated);

以上就是淺析C++ new的三種面貌的詳細內容,更多關於C++ new的資料請關注我們其它相關文章!