1. 程式人生 > >C++之寫了placement new也要寫placement delete(52)---《Effective C++》

C++之寫了placement new也要寫placement delete(52)---《Effective C++》

條款52:寫了placement new也要寫palcement delete

問題:

Widget* pw=new Widget;

這條語句的執行導致兩個函式被使用:一個是用以分配記憶體的operator new,另一個是Widget的default建構函式。假設其中第一個函式呼叫成功,第二個函式丟擲異常。這樣第一步中operator new的記憶體分配所得必須取消並恢復舊觀,否則會造成記憶體洩漏,這個時候,客戶沒有能力歸還記憶體,因為如果Widget建構函式丟擲異常,pw當前尚未被賦值,客戶手上就沒有相應的指標執行指向該被歸還的記憶體,取消第一步並恢復舊觀的重任就變成了C++執行期系統的重任。

執行期系統會呼叫第一步中operator new的相應operator delete版本,前提是它必須知道哪一個operator delete被呼叫:

//正常的operator new
void * operator new(std::size_t) throw(std::bad_alloc);
//正常的operator delete
//1、global作用域中的正常簽名式
void operator delete(void* rawMemory) throw();
//2、class作用域中典型的簽名式
void operator delete(void* rawMemory,std::size_t size) throw
();

然而當我們宣告一個非正常形式的operator new,也就是帶有附加引數的operator new時,那麼該呼叫那個operator delete呢?

class Widget{
public:
    ...
    static void* operator new(std::size_t,std::ostream& logStream) throw(std::bad_alloc);//非正常形式new
static void operator delete(void* pMemory std::size_t size) throw()//正常的class專屬delete
    ...
};

1、placement new/delete的提出

如果operator new接受的引數除了一定會有的那個size_t之外還有其他,這便是所謂的placement new,眾多的placement new版本中有一個“接受一個指標指向物件該被構造之處”,這種placement new已經被納入C++標準程式庫,呼叫#include <new>就可以取用它。這種operator new的樣子如下所示:

void* operator new(std::size_t,void* pMemory) throw();

上面我們針對Widget已經聲明瞭一種placement new版本,如何使用它呢?下面舉一個例子便於大家理解:

Widget* pw=new (std::cerr)Widget;//呼叫operator new並傳遞cerr作為其ostream實參,這個動作會在Widget建構函式丟擲異常時洩漏記憶體

那麼此時如果丟擲異常的話怎樣取消operator new的分配並恢復舊觀,由於執行期間系統無法知道真正被呼叫的那個operator new如何運作,因此它無法取消分配並恢復舊觀,取而代之得失C++執行期系統尋找“引數個數和型別都與operator new相同的”operator delete。如果找到,就是它的呼叫物件。那麼Widget的這種形式的operator new對應的operator delete版本應該是:

void operator delete(void*,std::ostream&) throw();

這也就引出了placement delete的定義,如果operator delete如果接受額外引數,便稱為placement delete

下面我們寫一個class專屬的operator new,要求接受一個ostream,用來志記相關分配資訊,同時又洗了一個正常形式的class專屬operator delete:

class Widget{
public:
    ...
    static void* operator new(std::size_t size,std::ostream& logStream) throw(std::bad_alloc);
    static void operator delete(void* pMemory) throw();
    static void operator delete(void* pMemory,std::ostream& logStream) throw();
    ...
};

2、placement new的繼承

class Base{
public:
    ...
    static void* operator new(std::size_t size,std::ostream& logStream) throw(std::bad_alloc);//這個new會遮掩正常的global形式
    ...
};
Base* pb=new Base;//錯誤!因為正常形式的operator new被遮掩
Base* pb=new (std::cerr)Base;//正確,呼叫Base的placement new
class Derived:public Base{
public: 
    ...
    static void* operator new(std::size_t size);
    ...
};
Derived* pd=new (std::clog)Derived;//錯誤,因為Base中的placement new被遮掩了
Derived* pd=new Derived;//正確

要解決遮掩問題的話,我們需要家裡一個Base class,內含所有正常形式的new和delete:

class StandardNewDeleteForms{
public: 
    static void* operator new(std::size_t size) throw(std::bad_alloc){
        return ::operator new(size);
    }
    static void opearator delete(void* pMemory)throw(){
        ::operator delete(pMemory);
    //placement new/delete
    static void* operator new(std::size_t size,void*ptr)throw(){
        return ::operator new(size,ptr);
    }
    static void operator delete(void* pMemory,void* ptr)throw(){
        return ::operator delete(pMemory,ptr);
    }
    static void* operator new(std::size_t size,const std::nothrow_t& nt) throw(){
        return ::operator new(size,nt);
    }
    static void operator delete(void* pMemory,const std::nothrow_t&nt) throw(){
        ::operator delete(pMemory);
    }
};  
class Widget::public StandardNewDeleteForms{
public:
    using StandardNewDeleteForms::operator new;
    using StandardNewDeleteForms::operator delete;
    static void* operator new(std::size_t size,std::ostream& logStream)throw(std::bad_alloc);
    static void operator delete(void* pMemory,std::ostream& logStream)throw();
    ...
};

PS:
C++中operator new/delete或者operator new[]/delete[]的解析方式:http://blog.csdn.net/hazir/article/details/21413833
總結:
1)當你寫了一個placement operator new,請確定也謝了對應的placement operator delete,如果沒有這樣做,程式可能會發生隱蔽而時斷時續的記憶體洩漏;
2)當你聲明瞭placement new和placement delete,請確定不要無意識地遮掩了它們的正常版本。

相關推薦

C++placement newplacement delete(52---《Effective C++》

條款52:寫了placement new也要寫palcement delete 問題: Widget* pw=new Widget; 這條語句的執行導致兩個函式被使用:一個是用以分配記憶體的operator new,另一個是Widget的default

Effective C++》讀書筆記item52:placement newplacement delete

1.當在類中聲明瞭一個placement new(一個特定位置上的new),它接受了除std::size_t外的其他變數作為引數,則必須同樣宣告一個placement delete並且其引數與placement new相同以取得對應關係,這樣當記憶體分配失敗時C++編譯器將

Effective C++】讀書筆記 條款52placement new placement delete

定製new 和 delete 條款52:寫了placement new 也要寫placement delete 1. new 操作中的記憶體洩漏 有如下一個new操作 A *pb = new A; //A是一個自定義class型別 我們知

這次無論如何下來。。哭

經歷了一週的ssm學習之後,也算是懂了皮毛了。下面發一個困擾了我一天的失誤。     package com.cn.lin.service.impl; import javax.annotation.Resource; import org.springfra

C++ Vector 簡單實現 會用

我們知道,記憶體塊的大小是不能改變的,因此陣列的大小不能改變。但是STL的vector讓我們擺脫了這種困擾,它可以幫我們動態的管理陣列的大小。 誠然,STL的vector底層還是通過動態陣列來實現的,

【10000+文章匯總】技巧都在這裏,你出1w+好文!

51cto博客 10000+ 自 #我要10000+# 計劃啟動以來,已經有多位作者參與其中,我們通過文章專屬推廣渠道,取得了驚人的效果!單篇文章的閱讀量,最高達到55倍的閱讀量增長。從默默無聞,到有人喜歡,獲得關註的同時,打造個人影響力。現在讓我們來看看,這些 10000+ 好文,都有哪些~標題閱讀

FPGA設計中遇到的奇葩問題“芯片看出身”(一

程序人生摘要: 昨夜西風雕碧樹。獨上高樓,望盡天涯路 2000年的時候,做設計基本都是使用Xilinx公司的Virtex和Virtex-E系列芯片。那時候Altera技術實力還比較弱,基於Altera的芯片做設計是要被大家diss的。昨夜西風雕碧樹。獨上高樓,望盡天涯路2000年的時候,做設計基本都是使用Xi

,結婚成區塊鏈智慧合約

在下面的文章中,我將深入介紹“智慧婚禮合約”的技術實施細節。 請記住,這是一個原型,因此產品功能可能並不齊全。   如果您對整體想法和概念感興趣,可以在這裡閱讀更多相關資訊。   介紹 該專案基本上

,結婚成區塊鏈智能合約

可能 進行 aes art 流行 crypto pan sender extern 在下面的文章中,我將深入介紹“智能婚禮合約”的技術實施細節。 請記住,這是一個原型,因此產品功能可能並不齊全。 如果您對整體想法和概念感興趣,可以在這裏閱讀更多相關信息。 介

即便沒有讀者,你部落格

導讀:正好昨天在微博又推薦了一條黃博文《敏捷地寫部落格》文章的評論,然後又在Hacker News上看到了 Nathan Marz 的這篇英文博文,真趕巧。後來才想起來,我們以前編譯的博文《開發者拒絕寫技術部落格的常見理由》中已經有過類似忠告:“就算你覺得沒人會看你

Flutterdrawer詳細分析(你的操作都有

1. 簡介 這篇文章主要講解有關drawer的一切。 另:接Flutter相關專案,需要的私信或通過QQ:708959817,聯絡我 2. 初探 我們先來看看簡單的drawer在Flutter的應用 class HomePage extends StatefulWidget {

placement new 為什麼不能用delete來刪除

https://blog.csdn.net/D_Guco/article/details/54019495     在c++中new和delete操作是我們操作記憶體最常用的一對操作符,在使用new時編譯器會申請記憶體,然後呼叫

Spark 效能調優 Rdd reduceByKey 本地聚合(就是map端聚合運算元

簡單程式碼 val lines = sc.textFile("hdfs://") val words = lines.flatMap(_.split(" ")) val pairs = words.map((_, 1)) val counts = pairs.reduceByKey(_

C++複製物件時勿忘每一個成分(12---《Effective C++》

條款12:賦值物件時勿忘其每一個成分 C++中設計良好的物件系統會將物件的內部封裝起來,只留下兩個函式負責物件那個拷貝(賦值),即copy建構函式和copy operator=。 如果我們自己宣告自己的copying函式,則C++的編譯器則不會對我們自己提供

C++訪問控制(public、private、protected以及friend

public  所有均可訪問private 類自己的成員函式訪問,不能被類物件訪問protected 類自己以及子類訪問,不能被類物件訪問friend 友元,別人是你的朋友,他可以訪問我的東西。(但不是我可以訪問他的東西)友元關係不能被繼承。友元關係是單向的,不具有交換性。若

Objective-CAutorelease Pool底層實現原理記錄(雙向連結串列以及在Runloop中是如何參與進去的

最近需要重新整理知識點備用,把一些重要的原理都搞了一遍 前言 int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, a

C++指標陣列和陣列指標的區別---補充(9Effective C++》

指標陣列: 首先這是一個數組,陣列中的值型別是指標,通常表現為: template <typename T> T* Tname[10]; 現在我們這兒利用二維陣列進行舉例: int a[3][4]; int *p[3]; p[0]=a

C++返回值為reference引用的情況---補充(6Effective C++》

上篇部落格中我們講了返回一個reference物件可能會出錯或者效率特別低,那有沒有比較適合返回reference引用的情況呢?下面我們就來總結一下型別: 1)函式返回值用引用,引數傳遞進去也用引用: int& hel(int& t){

C# FTP伺服器中檔案上傳與下載(二

        通過上一篇部落格《C# 之 FTP伺服器中檔案上傳與下載(一)》,我們已經建立好了一個FTP伺服器,並且該伺服器需要使用者名稱和密碼的驗證。今天我們來實現檔案的上傳。 首先,我們前臺需要一個FileUpload控制元件和一個Button控制元件 <

C++ 多型(非常非常重要,重點在後面

開發十年,就只剩下這套架構體系了! >>>