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屬性,因此無法虛繼承、無法訪問類的非靜態成員。