C++中過載new和delete的使用
某些應用程式對記憶體分配有特殊的需求,因此我們無法將標準記憶體管理機制直接應用於這些程式。它們常常需要自定義記憶體分配的細節,比如使用關鍵字new將物件放置在特定的記憶體空間中。為了實現這一目的,應用程式需要過載new運算子和delete運算子以控制記憶體分配的過程。
儘管能夠”過載new和delete”,但是實際上過載這兩個運算子與過載其它運算子的過程大不相同。要想真正掌握過載new和delete的方法,首先要對new表示式和delete表示式的工作機理有更多的瞭解。
當我們使用一條new表示式時:
string* sp = new string("a value"); // 分配並初始化一個string物件
string* arr = new string[10]; // 分配10個預設初始化的string物件
實際執行了三個步驟:第一步,new表示式呼叫一個名為operator new(或者operator new[])的標準庫函式。該函式分配一個足夠大的、原始的、未命名的記憶體空間以便儲存特定型別的物件(或者物件的陣列)。第二步,編譯器執行相應的建構函式以構造這些物件,併為其傳入初始值。第三步,物件被分配了空間並構造完成,返回一個指向該物件的指標。當我們使用一條delete表示式刪除一個動態分配的物件時:
delete sp; // 銷燬*sp,然後釋放sp指向的記憶體空間
delete[] arr; // 銷燬陣列中的元素,然後釋放對應的記憶體空間
實際執行了兩步操作:第一步,對sp所指的物件或者arr所指的陣列中的元素執行對應的解構函式。第二步,編譯器呼叫名為operator delete(或者operator delete [])的標準庫函式釋放記憶體空間。如果應用程式希望控制記憶體分配的過程,則它們需要定義自己的operator new函式和operator delete函式。即使在標準庫中已經存在這兩個函式的定義,我們仍舊可以定義自己的版本。編譯器不會對這種重複的定義提出異議,相反,編譯器將使用我們自定義的版本替換標準庫定義的版本。
當定義了全域性operator new函式和operator delete函式後,我們就擔負起了控制動態記憶體分配的職責。這兩個函式必須是正確的:因為它們是程式整個處理過程中至關重要的一部分。
應用程式可以在全域性作用域中定義operator new函式和operator delete函式,也可以將它們定義為成員函式。當編譯器發現一條new表示式或delete表示式後,將在程式中查詢可供呼叫的operator函式。如果被分配(釋放)的物件是類型別,則編譯器首先在類及其基類的作用域中查詢。此時如果該類含有operator new成員或operator delete成員,則相應的表示式將呼叫這些成員。否則,編譯器在全域性作用域查詢匹配的函式。此時如果編譯器找到了使用者自定義的版本,則使用該版本執行new表示式或delete表示式;如果沒找到,則使用標準庫定義的版本。
我們可以使用作用域運算子令new表示式或delete表示式忽略定義在類中的函式,直接執行全域性作用域中的版本。例如,::new只在全域性作用域中查詢匹配的operator new函式,::delete與之類似。
標準庫定義了operatornew函式和operator delete函式的8個過載版本。其中前4個版本可能丟擲bad_alloc異常,後4個版本則不會丟擲異常:
//這些版本可能丟擲異常
void* operator new(size_t); // 分配一個物件
void* operator new[] (size_t); // 分配一個陣列
void* operator delete(void*) noexcept; // 釋放一個物件
void* operator delete[] (void*) noexcept; //釋放一個陣列
//這些版本承諾不會丟擲異常void* operator new(size_t, nothrow_t&) noexcept;
void* operator new[](size_t, nothrow_t&) noexcept;
void* operator delete(void*, nothrow_t&) noexcept;
void* operator delete[] (void*, nothrow_t&) noexcept;
型別nothrow_t是定義在new標頭檔案中的一個struct,在這個型別中不包含任何成員。new標頭檔案還定義了一個名為nothrow的const物件,使用者可以通過這個物件請求new的非丟擲版本。與解構函式類似,operator delete也不允許丟擲異常。當我們過載這些運算子時,必須使用noexcept異常說明符指定其不丟擲異常。應用程式可以自定義上面函式版本中的任意一個,前提是自定義的版本必須位於全域性作用域或者類作用域中。當我們將上述運算子函式定義成類的成員時,它們是隱式靜態的。我們無需顯示地宣告static,當然這麼做也不會引發錯誤。因為operator new用在物件構造之前而operator delete用在物件銷燬之後,所以這兩個成員(new和delete)必須是靜態的,而且它們不能操作類的任何資料成員。
對於operator new函式或者operator new[]函式來說,它的返回型別必須是void*,第一個形參的型別必須是size_t且該形參不能含有預設實參。當我們為一個物件分配空間時使用operator new;為一個陣列分配空間時使用operator new[]。當編譯器呼叫operator new時,把儲存指定型別物件所需的位元組數傳給size_t形參;當呼叫operator new[]時,傳入函式的則是儲存陣列中所有元素所需的空間。
如果我們想要定義operator new函式,則可以為它提供額外的形參。此時,用到這些自定義函式的new表示式必須使用new的定位形式將實參傳給新增的形參。儘管在一般情況下我們可以自定義具有任何形參的operator new,但是下面這個函式卻無論如何不能被使用者過載:
void* operator new(size_t, void*); // 不允許重新定義這個版本
這種形式只供標準庫使用,不能被使用者重新定義。
對於operator delete函式或者operator delete[]函式來說,它們的返回型別必須是void,第一個形參的型別必須是void*。執行一條delete表示式將呼叫相應的operator函式,並用指向待釋放記憶體的指標來初始化void*形參。
當我們將operator delete或operator delete[]定義成類的成員時,該函式可以包含另外一個型別為size_t的形參。此時,該形參的初始值是第一個形參所指物件的位元組數。size_t形參可用於刪除繼承體系中的物件。如果基類有一個虛解構函式,則傳遞給operator delete的位元組數將因待刪除指標所指物件的動態型別不同而有所區別。而且,實際執行的operator delete函式版本也由物件的動態型別決定。
new表示式與operator new函式:標準庫函式operator new和operator delete的名字容易讓人誤解。和其它operator函式不同(比如operator =),這兩個函式並沒有過載new表示式或delete表示式。實際上,我們根本無法自定義new表示式或delete表示式的行為。一條new表示式的執行過程總是先呼叫operator new函式以獲取記憶體空間,然後在得到的記憶體空間中構造物件。與之相反,一條delete表示式的執行過程總是先銷燬物件,然後呼叫operator delete函式釋放物件所佔的空間。我們提供新的operator new函式和operator delete函式的目的在於改變記憶體分配的方式,但是不管怎樣,我們都不能改變new運算子和delete運算子的基本含義。
malloc函式與free函式:malloc函式接受一個表示待分配位元組數的size_t,返回指向分配空間的指標或者返回0以表示分配失敗。free函式接受一個void*,它是malloc返回的指標的副本,free將相關記憶體返回給系統。呼叫free(0)沒有任何意義。
定位new表示式:儘管operator new函式和operator delete函式一般用於new表示式,然而它們畢竟是標準庫的兩個普通函式,因此普通的程式碼也可以直接呼叫它們。在C++的早期版本中,allocator類還不是標準庫的一部分。應用程式如果想把記憶體分配與初始化分離開來的話,需要呼叫operator new和operator delete。這兩個函式的行為與allocator的allocate成員和deallocate成員非常類似,它們負責分配或釋放記憶體空間,但是不會構造或銷燬物件。
與allocator不同的是,對於operator new分配的記憶體空間來說我們無法使用construct函式構造物件。相反,我們應該使用new的定位new (placement new)形式構造物件。如我們所知,new的這種形式為分配函式提供了額外的資訊。我們可以使用定位new傳遞一個地址,此時定位new的形式如下所示:
new(place_address) type
new(place_address) type (initializers)
new(place_address) type [size]
new(place_address) type [size] { braced initializer list }
其中place_address必須是一個指標,同時在initializers中提供一個(可能為空的)以逗號分隔的初始值列表,該初始值列表將用於構造新分配的物件。當僅通過一個地址值呼叫時,定位new使用operator new(size_t, void*)”分配”它的記憶體。這是一個我們無法自定義的operator new版本。該函式不分配任何記憶體,它只是簡單地返回指標實參;然後由new表示式負責在指定的地址初始化物件以完成整個工作。事實上,定位new允許我們在一個特定的、預先分配的記憶體地址上構造物件。當只傳入一個指標型別的實參時,定位new表示式構造物件但是不分配記憶體。
儘管在很多時候使用定位new與allocator的construct成員非常相似,但在它們之間也有一個重要的區別。我們傳給construct的指標必須指向同一個allocator物件分配的空間,但是傳給定位new的指標無需指向operator new分配的記憶體。實際上傳給定位new表示式的指標甚至不需要指向動態記憶體。
顯示的解構函式呼叫:就像定位new與使用allocate類似一樣,對解構函式的顯示呼叫也與使用destroy很類似。我們既可以通過物件呼叫解構函式,也可以通過物件的指標或引用呼叫解構函式,這與呼叫其它成員函式沒什麼區別:
string* sp = new string("a value"); // 分配並初始化一個string物件
sp->~string()
在這裡我們直接呼叫了一個解構函式。箭頭運算子解引用指標sp以獲得sp所指的物件,然後我們呼叫解構函式,解構函式的形式是波浪線(~)加上型別的名字。和呼叫destroy類似,呼叫解構函式可以清除給定的物件但是不會釋放該物件所在的空間。如果需要的話,我們可以重新使用該空。呼叫解構函式會銷燬物件,但是不會釋放記憶體。The new and delete operators can also be overloaded like other operators in C++. New and Delete operators can be overloaded globally or they can be overloaded for specific classes.
If these operators are overloaded using member function for a class, it means that these operators are overloaded only for that specific class.
If overloading is done outside a class (i.e. it is not a member function of a class), the overloaded ‘new’ and ‘delete’ will be called anytime you make use of these operators (within classes or outside classes). This is global overloading.
以上內容主要摘自:《C++Primer(Fifth Edition 中文版)》第19.1章節
關於其它operator函式的使用可以參考: http://blog.csdn.net/fengbingchun/article/details/51292506
下面是從其他文章中copy的測試程式碼,詳細內容介紹可以參考對應的reference:
#include "operator_new.hpp"
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <string>
namespace operator_new_ {
////////////////////////////////////////////////////////////////
// reference: http://zh.cppreference.com/w/cpp/memory/new/operator_new
class New1 {
public:
New1() = default;
void* operator new(std::size_t sz){
std::printf("global op new called, size = %zu\n", sz);
return std::malloc(sz);
}
void operator delete(void* ptr) /*noexcept*/
{
std::puts("global op delete called");
std::free(ptr);
}
};
struct New2 {
static void* operator new(std::size_t sz)
{
std::cout << "custom new for size " << sz << '\n';
return ::operator new(sz);
}
static void* operator new[](std::size_t sz)
{
std::cout << "custom new for size " << sz << '\n';
return ::operator new(sz);
}
static void operator delete(void* ptr, std::size_t sz)
{
std::cout << "custom delete for size " << sz << '\n';
::operator delete(ptr);
}
static void operator delete[](void* ptr, std::size_t sz)
{
std::cout << "custom delete for size " << sz << '\n';
::operator delete(ptr);
}
};
struct New3 {
New3() { throw std::runtime_error(""); }
static void* operator new(std::size_t sz, bool b){
std::cout << "custom placement new called, b = " << b << '\n';
return ::operator new(sz);
}
static void operator delete(void* ptr, bool b)
{
std::cout << "custom placement delete called, b = " << b << '\n';
::operator delete(ptr);
}
};
int test_operator_new_1()
{
New1* new1 = new New1;
delete new1;
New2* p1 = new New2;
delete p1;
New2* p2 = new New2[10];
delete[] p2;
try {
New3* p1 = new (true) New3;
} catch (const std::exception&) {}
return 0;
}
///////////////////////////////////////////////////////////////
// https://www.geeksforgeeks.org/overloading-new-delete-operator-c/
class student
{
std::string name;
int age;
public:
student()
{
std::cout << "Constructor is called\n";
}
student(std::string name, int age)
{
std::cout << "Constructor params is called\n";
this->name = name;
this->age = age;
}
void display()
{
std::cout << "Name:" << name << std::endl;
std::cout << "Age:" << age << std::endl;
}
void * operator new(size_t size)
{
std::cout << "Overloading new operator with size: " << size << std::endl;
void * p = ::new student();
//void * p = malloc(size); will also work fine
return p;
}
void operator delete(void * p)
{
std::cout << "Overloading delete operator " << std::endl;
free(p);
}
};
int test_operator_new_2()
{
student * p = new student("Yash", 24);
p->display();
delete p;
return 0;
}
////////////////////////////////////////////////////////////////
// reference: http://thispointer.com/overloading-new-and-delete-operators-at-global-and-class-level/
class Dummy
{
public:
Dummy()
{
std::cout << "Dummy :: Constructor" << std::endl;
}
~Dummy()
{
std::cout << "Dummy :: Destructor" << std::endl;
}
// Overloading CLass specific new operator
static void* operator new(size_t sz)
{
void* m = malloc(sz);
std::cout << "Dummy :: Operator new" << std::endl;
return m;
}
// Overloading CLass specific delete operator
static void operator delete(void* m)
{
std::cout << "Dummy :: Operator delete" << std::endl;
free(m);
}
};
int test_operator_new_3()
{
Dummy * dummyPtr = new Dummy;
delete dummyPtr;
return 0;
}
//////////////////////////////////////////////////////////////
// reference: https://msdn.microsoft.com/en-us/library/kftdy56f.aspx
class Blanks
{
public:
Blanks() { std::cout << "Constructor " << std::endl; }
void* operator new(size_t stAllocateBlock, char chInit);
};
void* Blanks::operator new(size_t stAllocateBlock, char chInit)
{
std::cout << "size:" << stAllocateBlock << ",chInit:" << chInit << "end" << std::endl;
void *pvTemp = malloc(stAllocateBlock);
if (pvTemp != 0)
memset(pvTemp, chInit, stAllocateBlock);
return pvTemp;
}
int test_operator_new_4()
{
Blanks *a5 = new(0xa5) Blanks;
std::cout << (a5 != 0) << std::endl;
return 0;
}
////////////////////////////////////////////////////////////
// reference: http://www.interviewsansar.com/2015/07/15/write-syntax-to-overload-new-and-delete-operator-in-a-class/
class CustomMemory {
private:
int i; // size of int is 4 byte
public:
CustomMemory(){
std::cout << "Constructor" << "\n";
}
~CustomMemory(){
std::cout << "Destructor" << "\n";
}
//Overloaded new
void* operator new(size_t objectSize)
{
std::cout << "Custom memory allocation" << "\n";
//Write allocation algorithm here
return malloc(objectSize);
}
//Overloaded 2 arguments new operator
void* operator new(size_t objectSize, int x)
{
std::cout << "Custom 2 argument memory allocation" << "\n";
CustomMemory *ptr = (CustomMemory*)malloc(objectSize);
ptr->i = x;
return ptr;
}
//Overloaded delete
void operator delete(void* ptr)
{
std::cout << "Custom memory de- allocation" << "\n";
free(ptr);
}
void Display()
{
std::cout << "Value of i =" << i << "\n";
}
};
int test_operator_new_5()
{
CustomMemory* obj = new CustomMemory(); // call overloaded new from the class delete obj;
delete obj; // call overloaded delete
//overloaded 2 argument new
CustomMemory * ptr = new(5)CustomMemory();
ptr->Display();
delete ptr;
return 0;
}
} // namespace operator_new_
GitHub: https://github.com/fengbingchun/Messy_Test
相關文章
- c++中new和delete的使用方法C++delete
- C++中動態建立和刪除陣列(new 和delete)C++陣列delete
- 如何檢測記憶體洩漏——過載new和delete記憶體delete
- 【C++學習筆記】一個使用new和delete的例項C++筆記delete
- C++記憶體管理:new / delete 和 cookieC++記憶體deleteCookie
- 程式設計中new[]和delete[]應該如何使用?程式設計delete
- C malloc() free(), C++ new() delete()C++delete
- C++語言基礎(2)-new和delete操作符C++delete
- C++之new、delete 與malloc、free的異同C++delete
- C++ Gotchas 條款62:替換Global New和Global Delete (轉)C++Godelete
- C++動態記憶體管理——new/deleteC++記憶體delete
- <七>深入理解new和delete的原理delete
- malloc/free 和 new /delete 的區別 (轉)delete
- C/C++中的new/delete、構造/解構函式、dynamic_cast分析C++delete函式AST
- JavaScript中的new map()和new set()使用詳細(new map()和new set()的區別)JavaScript
- 《Effective C++》第8章 定製new和delete-讀書筆記C++delete筆記
- 第四篇:new和delete的基本用法delete
- C++ new A 和 new A() 的區別詳解C++
- 關於C++ delete 來釋放new分配的記憶體C++delete記憶體
- C++中運算子的過載C++
- malloc free與new delete的區別和聯絡delete
- js中new關鍵字的使用過程JS
- C++ 過載運算子和過載函式C++函式
- C/C++—— C++中函式重寫和函式過載C++函式
- 記憶體(new delete )記憶體delete
- array new 與 array deletedelete
- 【50】瞭解new和delete的合理替換時機delete
- C++ new 和異常C++
- 【C++】C++用new和不用new建立類物件區別C++物件
- new/delete 和malloc/free 的區別一般彙總delete
- 【51】編寫new和delete時需固守常規delete
- C++ Gotchas 條款63:Member New和Member Delete之生存期與活動空間的迷惑 (轉)C++Godelete
- C++ 函式過載和模板C++函式
- 條款16 成對使用new和delete時要採取相同形式delete
- 軟體測試學習教程——過載new或delete來檢測記憶體洩漏delete記憶體
- RMAN 中delete exipired 和 delete obsolete 的區別delete
- rman 中的 delete all input 和 delete input 的區別delete
- 118 C++中函式的過載C++函式