跳至內容

new (C++)

本頁使用了標題或全文手工轉換
維基百科,自由的百科全書

newC++程式語言中的一種語言結構,用於動態分配主記憶體、並用建構函式初始化分配的主記憶體。

new的使用稱為「new運算子表達式」,其內部實現分為兩步:

  1. 呼叫相應的operator new()函數,動態分配主記憶體。如果operator new()不能成功獲得主記憶體,則呼叫new_handler函數。如果沒有設置new_handler函數或者new_handler未能分配足夠主記憶體,則投擲std::bad_alloc異常。「new運算子表達式」所呼叫的operator new()函數,按照C++的名字尋找英語name lookup規則,首先做依賴於實參的名字尋找(即ADL規則),在要申請主記憶體的資料類型T的內部、資料類型T定義處的命名空間尋找;如果沒有尋找到,則直接呼叫全域的::operator new()函數。
  2. 在分配到的動態主記憶體塊上初始化相應類型的對象並返回其首地址。如果呼叫建構函式初始化對象時投擲異常,則自動呼叫operator delete()函數釋放已經分配到的主記憶體。

每個new取得的對象,必須用delete解構並釋放主記憶體,以免記憶體流失

new運算子表達式是C++的一種語言結構,不可多載。但用戶可多載operator new()函數。

new運算子表達式語法

[編輯]

普通的new運算子表達式

[編輯]

new的語法是:

p_var = new typename;

其中p_var是已經定義的類型為typename的指標變數。

通過new初始化對象,使用下述語法:

p_var = new type(initializer); // 或者如此初始化 new type{initializer};

其中initializer是傳遞給建構函式的實參表或初值。

動態生成對象陣列的new運算子表達式

[編輯]

new也可建立一個對象陣列,稱之為「array forms new」:

p_var = new type [size];

C++98標準規定,new建立的對象陣列不能被顯式初始化, 陣列所有元素被預設初始化。如果陣列基本類型沒有預設初始化,則編譯報錯。但C++11已經允許顯式初始化,例如:

int *p_int = new int[3] {1,2,3};

如此生成的對象陣列,在釋放時必須呼叫delete [ ]表達式。例如

 delete [] p_int ;

帶位置的new運算子表達式

[編輯]

帶位置的new (placement new)的語法是:

new ( expression-list ) new-type-id ( optional-initializer-expression-list );

其中,expression-list將作為operator new()函數的實參列表的結尾部分。這種形式的new運算子表達式首先呼叫operator new(size_t,OtherTypeList)函數來取得主記憶體;然後對該對象執行建構函式。這裏的OtherTypeList作為形參列表要和new表達式中第一個括號里的實參列表expression-list的類型相容(即形參實參能夠匹配)。

帶位置的new運算子,語意上包括四種使用情形,其中前兩種已經在標準標頭檔<new>中實現了:

  1. 直接給出要構建的對象的主記憶體位置;
  2. 不投擲異常,如果主記憶體分配失敗返回空指標;
  3. 客製化的、帶其他參數的主記憶體分配器;
  4. 用於除錯目的,在建構函式呼叫失敗時給出原始檔名與行號。

狹義上的帶位置的new是指第一種情形。使用這種placement new,原因之一是用戶的程式不能在一塊主記憶體上自行呼叫其建構函式(即用戶的程式不能顯式呼叫建構函式),必須由編譯系統生成的代碼呼叫建構函式。原因之二是可能需要把對象放在特定硬件的主記憶體地址上,或者放在多處理器內核的共用的主記憶體地址上。

釋放這種對象時,不能呼叫placement delete,應直接呼叫解構函式,如:pObj->~ClassType();然後再自行釋放主記憶體。

舉例:

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    char buf[100];
    int *p=new (buf) int(101);
    cout<<*(int*)buf<<endl;
    return 0;
}

保證不投擲異常的new運算子表達式

[編輯]

在分配主記憶體失敗時,new運算子的標準行為是投擲std::bad_alloc異常。也可以讓new運算子在分配主記憶體失敗時不投擲異常而是返回空指標。

new (nothrow) Type ( optional-initializer-expression-list );

new (nothrow) Type[size];

其中nothrowstd::nothrow_t的一個實例

自行客製化參數的new運算子表達式

[編輯]

new運算子的參數可以是任意合法類型的列表。由C++的多載機制來決定呼叫那個operator new。

new (Type_list) Type ( optional-initializer-expression-list );

new (Type_list) Type[size];

帶位置的delete運算子表達式

[編輯]

C++ 不能使用帶位置的 delete 運算子表達式直接解構一個對象但不釋放其主記憶體。因此,對於用廣義的帶位置new表達式構建的對象,解構釋放時有兩種辦法:

第一種辦法是直接寫一個函數,完成解構對象、釋放主記憶體的操作:

    void
    destroy (T * p, A & arena)
    {
        p->~T() ;   // First invoke the destructor explicitly.
        arena.deallocate(p) ;  // Then call the deallocator function directly.
    }

如此使用:

A arena ;
T * p = new (arena) T ;
/* ... */
destroy(p, arena) ;

第二種辦法是分兩步顯式呼叫解構函式與帶位置的operator delete函數:

A arena ;
T * p = new (arena) T ;
/* ... */
p->~T() ;    // First invoke the destructor explicitly.
operator delete(p, arena) ;   // Then call the deallocator function indirectly via operator delete(void *, A &) .

帶位置的operator delete()函數,可以被帶位置的new算符表達式自動呼叫。這是在對象的建構函式投擲異常的時候,用來釋放掉帶位置的operator new函數取得的主記憶體。以避免主記憶體泄露。

例如:

#include <cstdlib>
#include <iostream>
char buf[100];
struct A {} ;
struct E {} ;

class T {
public:
    T() { throw E() ; }
    void * operator new(std::size_t,const A &){
        std::cout << "Placement new called for class T." << std::endl;
        return buf;
    }
    void operator delete(void*, const A &)
    {
        std::cout << "Placement delete called for class T." << std::endl;
    }
} ;

void * operator new ( std::size_t, const A & )
    {std::cout << "Placement new called." << std::endl; return buf;}
void operator delete ( void *, const A & )
    {std::cout << "Placement delete called." << std::endl;}

int main ()
{
    A a ;
    try {
        T * p = new (a) T ;
        /* do something */
    } catch (E exp) {std::cout << "Exception caught." << std::endl;}
    return 0 ;
}

operator new()的函數多載

[編輯]

使用new動態生成一個對象,實際上是呼叫了new運算子表達式。該運算子首先呼叫了operator new函數動態分配主記憶體,然後呼叫類型的建構函式初始化這塊主記憶體。new運算子是不能被多載的,但是下述各種operator new()函數既可以作為全域函數多載,也可以作為類別成員函數或作用域內的函數多載,即由編程者指定如何取得主記憶體。

普通的operator new(size_t size)函數

[編輯]

new運算子呼叫operator new函數動態分配主記憶體。首先尋找類內是否有operator new函數可供使用(即依賴於實參的名字尋找)。[1]operator new函數的參數是一個size_t類型,指明了需要分配主記憶體的規模。[2]operator new函數可以被每個C++類別作為成員函數多載。也可以作為全域函數多載:

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

主記憶體需要回收的話,呼叫對應的operator delete()函數。

例如,在new運算子表達式的第二步,呼叫建構函式初始化主記憶體時如果投擲異常,例外處理機制在棧展開(stack unwinding)時,要回收在new運算子表達式的第一步已經動態分配到的主記憶體,這時就會自動呼叫對應operator delete()函數。

陣列形式的operator new[](size_t size)函數

[編輯]

new Type[]運算子(array forms new),用來動態建立一個對象陣列。這需要呼叫陣列基本類型內部定義的void* operator new[ ](size_t)函數來分配主記憶體。如果陣列基本類型沒有定義該函數,則呼叫全域的void* operator new[ ](size_t)函數來分配主記憶體。

<new>中聲明了void* operator new[ ](size_t)全域函數:

void * operator new[] (std::size_t) throw(std::bad_alloc);
void operator delete[](void*) throw();

void* operator new(size_t,void*)

[編輯]

operator new(size_t,void*)函數用於帶位置的new運算子呼叫。C++標準庫已經提供了operator new(size_t,void*)函數的實現,包含<new>標頭檔即可。這個實現只是簡單的把參數的指定的地址返回,帶位置的new運算子就會在該地址上呼叫建構函式來初始化對象:

// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) throw() { return __p; }
inline void* operator new[](std::size_t, void* __p) throw() { return __p; }

// Default placement versions of operator delete.
inline void  operator delete  (void*, void*) throw() { }
inline void  operator delete[](void*, void*) throw() { }

禁止重定義這4個函數。因為都已經作為<new>的行內函數了。在使用時,實際上不需要#include <new>

對應的placement delete函數,只應在placement new運算子表達式在第二步呼叫建構函式投擲異常時被例外處理機制的棧展開操作自動呼叫。

保證不投擲異常的operator new函數

[編輯]

C++標準庫的<new>中還提供了一個nothrow的實現,用戶可寫自己的函數替代:

void* operator new(std::size_t, const std::nothrow_t&) throw();
void* operator new[](std::size_t, const std::nothrow_t&) throw();
void operator delete(void*, const std::nothrow_t&) throw();
void operator delete[](void*, const std::nothrow_t&) throw();

自行客製化參數的operator new函數

[編輯]

這種函數被自行客製化參數的new算符呼叫。需要由用戶自行定義,以確定分配主記憶體時的行為:

operator new(size_,Type1, Type2, ... );

例如:

char data[1000][sizeof(int)];
inline void* operator new(size_t ,int n)
{
        return data[n];
}
void foo(){ 
    int *p=new(6) int(102); //把整型对象创建在data的第六个单元上
}

參見

[編輯]

參考文獻

[編輯]
  1. ^ 在class內定義operator new函數,被稱作per-class allocator語言支援。其原因為:
    第一、許多程式應用,需要在執行的過程中,大量地Create和Delete對象。這些對象,諸如:tree nodes,linked list nodes,messages等等。如果在傳統的heap完成這些對象的建立,銷毀,由於大量的主記憶體申請,釋放,勢必會造成主記憶體碎片。這種情況下,我們需要對主記憶體分配進行細粒度的控制。
    第二、一些應用需要長時間跑在主記憶體受限的裝置上,這也需要我們對主記憶體分配進行細粒度的控制,而不是無限制地分配,釋放。
  2. ^ operator new函數的參數是一個size_t類型,卻幾乎從不被用戶顯式使用。其解釋是:per-class allocator機制將適用整個類繼承體系,而不是面向單個類。對子類使用new運算子時,可能會呼叫父類別中定義的operator new()來取得主記憶體。但是,在這裏,主記憶體分配的大小,不應該是sizeof(父類別),而是sizeof(子類)。無論是否聲明,類裏面多載的各種operator new函數和operator delete函數都是具有static屬性,因此無法虛繼承、無法訪問類的非靜態成員。