成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

借來的資源,如何還的瀟灑?

存儲 存儲軟件
本文的內容將專門對付內存管理,培養起有借有還的好習慣,方可消除資源管理的問題。

[[326962]]

前言

本文的內容將專門對付內存管理,培養起有借有還的好習慣,方可消除資源管理的問題。

 

正文

所謂的資源就是,一旦用了它,將來必須還給系統。如果不是這樣,糟糕的事情就會發生。

C++ 程序內常見的資源:

  • 動態分配內存
  • 文件描述符
  • 互斥鎖
  • 圖形頁面中的字型和筆刷
  • 數據庫連接
  • 網絡 sockets

無論哪一種資源,重要的是,當你不再使用它時,必須將它還給系統,有借有還是個好習慣。

[[326963]]

 

細節 01 :以對象管理資源

把資源放在析構函數,交給析構函數釋放資源

假設某個 class 含有個工廠函數,該函數獲取了對象的指針:

  1. A* createA();    // 返回指針,指向的是動態分配對象。 
  2.                  // 調用者有責任刪除它。 

如上述注釋所言,createA 的調用端使用了函數返回的對象后,有責任刪除它?,F在考慮有個f函數履行了這個責任:

  1. void f() 
  2.     A *pa = createA();  // 調用工廠函數 
  3.     ...                 // 其他代碼 
  4.     delete pa;          // 釋放資源 

這看起來穩妥,但存在若干情況f函數可能無法執行到delete pa語句,也就會造成資源泄漏,例如如下情況:

  • 或許因為「…」區域內的一個過早的 return 語句;
  • 或許因為「…」區域內的一個循環語句過早的continue 或 goto 語句退出;
  • 或許因為「…」區域內的語句拋出異常,無法執行到 delete。

當然可以通過謹慎地編寫程序可以防止這一類錯誤,但你必須想想,代碼可能會在時間漸漸過去后被修改,如果是一個新手沒有注意這一類情況,那必然又會再次有內存泄漏的可能性。

為確保 A 返回的資源都是被回收,我們需要將資源放進對象內,當對象離開作用域時,該對象的析構函數會自動釋放資源。

「智能指針」是個好幫手,交給它去管理指針對象。

對于是由動態分配(new)于堆內存的對象,指針對象離開了作用域并不會自動調用析構函數(需手動delete),為了讓指針對象能像普通對象一樣,離開作用域自動調用析構函數回收資源,我們需要借助「智能指針」的特性。

常用的「智能指針」有如下三個:

  • std::auto_ptr( C++ 98 提供、C++ 11 建議摒棄不用 )
  • std::unique_ptr( C++ 11 提供 )
  • std::shared_ptr( C++ 11 提供 )

std::auto_ptr

下面示范如何使用 std::auto_ptr 以避免 f 函數潛在的資源泄漏可能性:

  1. void f() 
  2.     std::auto_ptr<A> pa (createA()); // 調用工廠函數 
  3.     ...  // 一如既往的使用pa 
  4. }        // 離開作用域后,經由 auto_ptr 的析構函數自動刪除pa; 

這個簡單的例子示范「以對象管理資源」的兩個關鍵想法:

  • 獲得資源后立刻放進管理對象內。以上代碼中 createA 返回的資源被當做其管理者 auto_ptr 的初值,也就立刻被放進了管理對象中。
  • 管理對象運用析構函數確保資源釋放。不論控制流如何離開區塊,一旦對象被銷毀(例如當對象離開作用域)其析構函數自然會被自動調用,于是資源被釋放。

為什么在 C++11 建議棄用 auto_ptr 嗎?當然是 auto_ptr 存在缺陷,所以后續不被建議使用。

auto_ptr 有一個不尋常的特質:若通過「復制構造函數或賦值操作符函數」 copy 它們,它們會變成 null ,而復制所得的指針將獲取資源的唯一擁有權!

見如下例子說明:


  1. std::auto_ptr<A> pa1(createA()); // pa1 指向 createA 返回物 
  2.  
  3. std::auto_ptr<A> pa2(pa1); // 現在 pa2 指向對象,pa1將被設置為 null 
  4.  
  5. pa1 = pa2; // 現在 pa1 指向對象,pa2 將被設置為 null 

這一詭異的復制行為,如果再次使用指向為 null 的指針,那必然會導致程序奔潰。

意味著 auto_ptr 并非管理動態分配資源的神兵利器。

std::unique_ptr

unique_ptr 也采用所有權模型,但是在使用時,是直接禁止通過復制構造函數或賦值操作符函數 copy 指針對象,如下例子在編譯時,會出錯:


  1. std::unique_ptr<A> pa1(createA()); // pa1 指向 createA 返回物 
  2.  
  3. std::unique_ptr<A> pa2(pa1); // 編譯出錯! 
  4.  
  5. pa1 = pa2; // 編譯出錯! 

std::shared_ptr

shared_ptr 在使用復制構造函數或賦值操作符函數后,引用計數會累加并且兩個指針對象指向的都是同一個塊內存,這就與 unique_ptr、auto_ptr 不同之處。

  1. void f() 
  2.     std::shared_ptr<A> pa1(createA()); // pa1 指向 createA 返回物 
  3.  
  4.     std::shared_ptr<A> pa2(pa1); // 引用計數+1,pa2和pa1指向同一個內存 
  5.  
  6.     pa1 = pa2; // 引用計數+1,pa2和pa1指向同一個內存 

當一個對象離開作用域,shared_ptr 會把引用計數值 -1 ,直到引用計數值為 0 時,才會進行刪除對象。

由于 shared_ptr 釋放空間時會事先要判斷引用計數值的大小,因此不會出現多次刪除一個對象的錯誤。

小結 - 請記住

  • 為防止資源泄漏,請使用 RAII(Resource Acquisition Is Initaliaztion - 資源取得時機便是初始化時機) 對象,它們在構造函數中獲取資源,并在析構函數中是釋放資源
  • 兩個建議使用的 RAII classes 分別是 std::unique_ptr 和 std::shared_ptr。前者不允許 copy 動作,后者允許 copy 動作。但是不建議用 std::auto_ptr,若選 auto_ptr,復制動作會使它(被復制物)指向 null 。

細節 02:在資源管理類中小心 copying 行為

假設,我們使用 C 語音的 API 函數處理類型為 Mutex 的互斥對象,共有 lock 和 unlock 兩函數可用:

  1. void locak(Mutex *pm);  // 鎖定 pm 所指的互斥器 
  2. void unlock(Mutex* pm); // 將互斥器解除鎖定 

為確保絕不會忘記一個被鎖住的 Mutex 解鎖,我們可能會希望創立一個 class 來管理鎖資源。這樣的 class 要遵守 RAII 守則,也就是「資源在構造期間獲得,在析構釋放期間釋放」:

  1. class Lock 
  2. public
  3.     explicit Lock(Mutex *pm) // 構造函數 
  4.         : pMutex(pm) 
  5.     { 
  6.         lock(pMutex); 
  7.     } 
  8.  
  9.     ~Lock()  // 析構函數 
  10.     { 
  11.         unlock(pMutex); 
  12.     } 
  13. private: 
  14.     Mutex* pMutex; 
  15. }; 

這樣定義的 Lock,用法符合 RAII 方式:

  1. Mutex m;      //定義你需要的互斥鎖 
  2. ...  
  3. {                 // 建立一個局部區塊作用域 
  4.     Lock m1(&m);  // 鎖定互斥器 
  5.     ... 
  6. }                 // 在離開區塊作用域,自動解除互斥器鎖定 

這很好,但如果 Lock 對象被復制,會發生什么事情?

  1. Lock m1(&m);  // 鎖定m 
  2. Lock m2(&m1); // 將 m1 復制到 m2身上,這會發生什么? 

這是我們需要思考和面對的:「當一個 RAII 對象被復制,會發生什么事情?」大多數時候你會選擇以下兩種可能:

  • 禁止復制。如果 RAII 不允許被復制,那我們需要將 class 的復制構造函數和賦值操作符函數聲明在 private。
  • 使用引用計數法。有時候我們希望保有資源,直到它的最后一個對象被消耗。這種情況下復制 RAII 對象時,應該將資源的「被引用數」遞增。std::shared_ptr 便是如此。

如果前述的 Lock 打算使用使用引用計數法,它可以使用 std::shared_ptr 來管理 pMutex 指針,然后很不幸 std::shared_ptr 的默認行為是「當引用次數為 0 時刪除其所指物」那不是我們想要的行為,因為要對Mutex釋放動作是解鎖而非刪除。

 

幸運的是 std::shared_ptr 允許指定自定義的刪除方式,那是一個函數或函數對象。如下:

  1. class Lock 
  2. public
  3.     explicit Lock(Mutex *pm)   
  4.         : pMutex(pm, unlock)  // 以某個 Mutex 初始化 shared_ptr, 
  5.                               // 并以 unlock 函數為刪除器。 
  6.     { 
  7.         lock(pMutex.get());  // get 獲取指針地址 
  8.     } 
  9.  
  10. private: 
  11.     std::shared_ptr<Mutex> pMutex; // 使用 shared_ptr 
  12. }; 

請注意,本例的 Lock class 不再聲明析構函數。因為編譯器會自動創立默認的析構函數,來自動調用其 non-static 成員變量(本例為 pMutex )的析構函數。

而 pMutex 的析構函數會在互斥器的引用次數為 0 時,自動調用 std::shared_ptr 的刪除器(本例為 unlock )。

小結 - 請記住

  • 復制 RAII 對象必須一并復制它的所管理的資源(深拷貝),所以資源的 copying 行為決定 RAII 對象的 copying 行為。
  • 普通而常見的 RAII class copying 行為是:禁止 copying、執行引用計數法。

細節 03 :在資源類中提供對原始資源的訪問

智能指針「顯式」轉換,也就是通過 get 成員函數的方式轉換為原始指針對象。

上面提到的「智能指針」分別是:std::auto_ptr、std::unique_ptr、std::shared_ptr。它們都有訪問原始資源的辦法,都提供了一個 get 成員函數,用來執行顯式轉換,也就是它會返回智能指針內部的原始指針(的復件)。

舉個例子,使用智能指針如 std::shared_ptr 保存 createA() 返回的指針對象 :

  1. std::shared_ptr<A> pA(createA()); 

假設你希望以某個函數處理 A 對象,像這樣:

  1. int getInfo(const A* pA); 

你想這么調用它:

  1. std::shared_ptr<A> pA(createA()); 
  2. getInfo(pA);       // 錯誤??! 

 

會編譯錯誤,因為 getInfo 需要的是 A 指針對象,而不是類型為std::shared_ptr 的對象。

這時候就需要用 std::shared_ptr 智能指針提供的 get 成員函數訪問原始的資源:

  1. std::shared_ptr<A> pA(createA()); 
  2. getInfo(pA.get());   // 很好,將 pA 內的原始指針傳遞給 getInfo 

智能指針「隱式」轉換的方式,是通過指針取值操作符。

智能指針都重載了指針取值操作符(operator->和operator*),它們允許隱式轉換至底部原始指針:

  1. class A 
  2. public: 
  3.     bool isExist() const; 
  4.     ... 
  5. }; 
  6.  
  7. A* createA();  // 工廠函數,創建指針對象 
  8.  
  9. std::shared_ptr<A> pA(createA()); // 令 shared_ptr 管理對象資源 
  10.  
  11. bool exist = pA->isExist();    // 經由 operator-> 訪問資源 
  12. bool exist2 = (*pA).isExist(); // 經由 operator* 訪問資源 

多數設計良好的 classes 一樣,它隱藏了程序員不需要看到的部分,但是有程序員需要的所有東西。

 

所以對于自身設計 RAII classes 我們也要提供一個「取得其所管理的資源」的辦法。

小結 - 請記住

  • APIs 往往要求訪問原始資源,所以每一個 RAII class 應該提供一個「取得其所管理的資源」的辦法。
  • 對原始資源的訪問可能經由顯式轉換或隱式轉換。一般而言顯式轉換比較安全,但隱式轉換比較方便。

細節 04:成對使用 new 和 delete以下動作有什么錯?

  1. std::string* strArray = new std::string[100]; 
  2. ... 
  3. delete strArray; 

每件事情看起來都井然有序。使用了 new,也搭配了對應的 delete。但還是有某樣東西完全錯誤。strArray 所含的 100 個 string 對象中的 99 個不太可能被適當刪除,因為它們的析構函數很可能沒有被調用。

當使用 new ,有兩件事發生:

  • 內存被分配出來(通過名為 operator new 的函數)
  • 針對此內存會有一個或多個構造函數被調用

當使用 delete,也會有兩件事情:

  • 針對此內存會有一個或多個析構函數被調用
  • 然后內存才被釋放(通過名為 operator delete 的函數)

delete 的最大問題在于:即將被刪除的內存之內究竟有多少對象?這個答案決定了需要執行多少個析構函數。

 

對象數組所用的內存通常還包括「數組大小」的記錄,以便 delete 知道需要調用多少次析構函數。單一對象的內存則沒有這筆記錄。你可以把兩者不同的內存布局想象如下,其中 n 是數組大?。?/p>

當你對著一個指針使用 delete,唯一能夠讓 delete 知道內存中是否存在一個「數組大小記錄」的辦法就是:由你告訴它。如果你使用 delete 時加上中括號[],delete 便認定指針指向一個數組,否則它便認定指針指向一個單一對象:

  1. std::string* strArray = new std::string[100]; 
  2. std::string* strPtr = new std::strin; 
  3. ...  
  4. delete [] strArray;  // 刪除一個對象 
  5. delete strPtr;       // 刪除一個由對象組成的數組 

游戲規則很簡單:

如果你在 new 表達式中使用[],必須在相應的 delete 表達式也使用[]。

如果你在 new 表達式中不使用[],一定不要在相應的 delete 表達式使用[]。

小結 - 請記住

  • 如果你在 new 表達式中使用[],必須在相應的 delete 表達式也使用[]。如果你在 new 表達式中不使用[],一定不要在相應的 delete 表達式使用[]。

細節 05:以獨立語句將 newed 對象置入智能指針

假設我們有個以下示范的函數:

  1. int getNum(); 
  2. void fun(std::shared_ptr<A> pA, int num); 

現在考慮調用 fun:

  1. fun(new A(), getNum()); 

它不能通過編譯,因為 std::shared_ptr 構造函數需要一個原始指針,而且該構造函數是個 explicit 構造函數,無法進行隱式轉換。如果寫成這樣就可以編譯通過:

  1. fun(std::shared_ptr<A>(new A), getNum()); 

令人想不到吧,上述調用卻可能泄露資源。接下來我們來一步一步的分析為什么存在內存泄漏的可能性。

 

在進入 fun 函數之前,肯定會先執行各個實參。上述第二個實參只是單純的對getNum 函數的調用,但第一個實參 std::shared_ptr(new A) 由兩部分組成:

于是在調用 fun 函數之前,先必須做以下三件事:

  • 調用 getNum 函數
  • 執行 new A 表達式
  • 調用 std::shared_ptr 構造函數

那么他們的執行次序是一定如上述那樣的嗎?可以確定的是 new A 一定比std::shared_ptr 構造函數先被執行。但對 getNum 調用可以排在第一或第二或第三執行。

如果編譯器選擇以第二順位執行它:

  1. 執行 new A 表達式
  2. 調用 getNum 函數
  3. 調用 std::shared_ptr 構造函數

萬一在調用 getNum 函數發生了異常,會發生什么事情?在此情況下 new A 返回的指針將不會置入 std::shared_ptr 智能指針里,就存在內存泄漏的現象。

避免這類問題的辦法很簡單:使用分離語句。

分別寫出:

  1. 創建 A
  2. 將它置入一個智能指針內
  3. 然后再把智能指針傳遞給 fun 函數。

  1. std::shared_ptr<A> pA(new A); // 先構造智能指針對象 
  2. fun(pA, getNum()); // 這個調用動作絕不至于造成泄漏。 

以上的方式,就能避免原本由于次序導致內存泄漏發生。

小結 - 請記住

  • 以獨立語句將 newed (已 new 過) 對象存儲于智能指針內。如果不這樣做,一旦異常被拋出,有可能導致難以察覺的資源泄漏。

 

責任編輯:武曉燕 來源: 小林coding
相關推薦

2018-09-25 09:11:59

2012-03-30 16:40:15

云計算SaaS

2024-07-01 08:00:00

2019-07-01 15:29:46

SANNAS存儲系統

2018-05-21 15:14:10

2020-09-09 07:00:00

Kubernetes集群容器

2018-10-10 09:25:36

向上管理 資源

2019-01-03 15:10:40

JVM安全資源

2012-01-16 09:00:18

云計算高性能計算

2011-09-02 09:49:48

2020-11-23 08:48:00

Kubernetes容器開發

2022-07-12 16:49:27

蘋果自動駕駛Apple Car

2011-02-16 14:16:13

Hyper-V clo虛擬資源

2018-12-18 09:00:26

Kubernetes工作負載測試

2021-07-02 05:31:53

ReactSolidJS前端

2021-12-03 05:54:20

React組件前端

2024-01-15 06:50:00

模型資源

2021-03-01 11:38:06

惡意軟件macOS攻擊

2015-05-05 09:37:29

OpenStackNova資源統計

2019-11-04 11:28:11

機器學習人工智能計算機
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 中文字幕日韩欧美 | 婷婷综合在线 | 午夜精品91 | xxxxx黄色片 欧美一区免费 | 国产午夜精品一区二区三区在线观看 | 亚州精品天堂中文字幕 | 日韩电影一区 | 日韩成人 | 亚洲精品美女视频 | 国产成人精品午夜视频免费 | 亚洲综合在线一区二区 | 综合国产在线 | 男女羞羞免费网站 | 国产精品中文字幕在线 | 亚洲成人综合社区 | 欧美福利专区 | 国产乱码一二三区精品 | 精品中文字幕一区 | 久热久草 | 综合久| 国产激情视频网站 | 午夜一级黄色片 | 中文字幕日韩一区 | 日韩在线播放一区 | 中文字幕在线观看一区二区 | 成人天堂| 国产精品视频在线播放 | 日韩一区在线播放 | 夫妻午夜影院 | 欧美h| 青青久久久 | 国产精品美女久久久久aⅴ国产馆 | 一本色道精品久久一区二区三区 | 免费久久99精品国产婷婷六月 | 免费观看黄a一级视频 | 久操国产 | 成人免费淫片aa视频免费 | 亚av在线| 日韩电影在线一区 | 日韩成人 | 伊人久久综合 |