天天看點

C++動态記憶體new+delete+shared_ptr+unique_ptr

直接管理記憶體

C++中,動态記憶體的管理是通過一對運算符來完成的:new+delete。

  • new在動态記憶體中為對象配置設定空間并傳回一個指向該對象的指針,可以選擇對其進行初始化。
  • delete 接受一個動态對象的指針,銷毀該對象,并釋放與之關聯的記憶體。

new+delete用法

int *pi=new int;//new建立了一個動态的,未初始化的無名對象,傳回指向該對象的指針
int *pi=new int(); //,在類型名後跟一對圓括号即代表初始化了。這例初始化0了
string *ps=new string; //初始化為空的string,因為相當于類類型對象将用預設構造函數進行初始化
string *ps=new string(); 

// 支援構造函數用法
string *ps=new string(5,'a');
int *pi=new int(5);
vector<int> *pv=new vector<int>{0,2,3};

// 可以使用auto讓初始化器推斷對象類型。此方法由于編譯器要用初始化器的類型來推斷要配置設定的類型,隻有當括号中僅有一初始化器時才可以使用auto
auto p1= new auto(obj);
// auto p2 = new auto{a,b,c}//錯誤,括号中隻能有的那個初始化器

// 動态配置設定的const對象
const int *pci=new const int(1024);
const string *pcs = new const string;// 類似于其他const對象,動态配置設定的const對象必須進行初始化。這裡的string是隐式初始化了

// 配置設定銷毀數組
int *pa = new int[3];
delete [] pa;
           

記憶體耗盡

抛出bad_alloc異常。

int *p1=new int;//若配置設定失敗則抛異常bad_alloc
int *p2=new (nothrow) int;// 若配置設定失敗,new傳回一個空指針。意思是不能抛異常
// bad_alloc和nothrow都定義在頭檔案new中
           

delete釋放動态記憶體

delete p;//p必須是指向動态配置設定的記憶體

int i,*pi1=&i,pi2=nullptr; 
//delete pi1; //未定義:pi1指向一個局部變量

// 釋放一個空指針總是沒有錯誤的

//const的指針也是可以銷毀的
           

通常情況下,編譯器不能分辨一個指針指向的是靜态還是動态的對象。類似地,編譯器也不能分辨一個指針所指向的記憶體是否已經釋放了。是以delete表達式,大多數編譯器都會編譯通過。盡管是錯誤的

C++11智能指針shared_ptr 、unique_ptr

為了更容易安全地使用動态記憶體,C++11标準庫提供了兩種智能指針類型來管理動态對象。智能指針的行為類似于正常指針,重要的差別是它負責自動釋放所指向的對象。新标準庫提供的這兩種智能指針的差別在于管理底層指針的方式:

  • shared_ptr 允許多個指針指向同一個對象
  • unique_ptr 則獨占所指向的對象

标準庫還定義了一個名為weak_ptr的伴随類,它是一種弱引用,指向shared_ptr所管理的對象。

這三種類型都定義在memory頭檔案中。

#include<memory>

shared_ptr類

類似vector,智能指針也是模闆。

#include <memory>
shared_ptr<string> p1; //可以指向string
shared_ptr<list<int>> p2; //可以指向int的list
           

智能指針的使用方式與普通指針類似。解引用一個智能指針傳回它所指向的對象。

#include <memory>
shared_ptr<string> p1; 
if(p1 && p1->empty)  // 第一個是p1不為空,第二個是檢查指向一個空string
    *p1 = "hi"; // 取内容解引用
           

下表列出了shared_ptr和unique_ptr都支援的操作

// shared_ptr和unique-ptr都支援的操作
shared_ptr<T> sp // 智能指針,可以指向類型為T的對象
unique_ptr<T> up

p // 将p用作一個條件判斷,若p指向一個對象,則為true
*p // 解引用p獲得它指向的對象
p->mem // 等價于(*p).mem
p.get() //傳回p中儲存的指針。要小心使用,若隻能指針釋放了其對象傳回的指針所指向的對象也就消失了。是以盡量不要把裸指針取出來
swap(p,q) // 交換p、q中的指針
p.swap(q) 


// 下面是shared_ptr獨有的操作
make_shared<T>(args)//傳回一個share_ptr,指向一個動态配置設定的類型為T的對象。使用args初始化此對象
share_ptr<T>p(q)// p是shared_ptr q的拷貝此操作會遞增q中的計數器。q中的指針必須能轉換為T*(參見4.11.2 I,第143頁)
p=q //p和q都是shared_ptr,所儲存的指針必須能互相轉換。此操作會遞減p的引用計數,遞増q的引用計數若p的引用計數變為0,則将其管理的原記憶體釋放
p.unique() //若p.use_count()為l,傳回true否則傳回false
p.use_count() // 傳回與p共享對象的智能指針數量可能很慢,主要用于調試
           

make_shared 函數

最安全的配置設定和使用動态記憶體的方法是調用一個名為make_shared的标準庫函數。

此函數在動态記憶體中配置設定一個對象并初始化它,傳回指向此對象的shared_ptr。

#include <memory>  
shared_ptr<int> p3 = make_shared<int>(42);// 指向一個值為42的int 的shared_ptr
shared_ptr<string> p4 = make_shared<string>(10,'9'); //指向一個值為'999..'的string
    //即傳入的形式是與string構造函數符合的
shared_ptr<int> p5 = make_shared<int>(); //指向一個值初始化的int,值為0
 // 不傳遞任何參數,對象就會進行初始化
 
// 也可以用auto接收
auto p6 = maked_ptr<vector<string>>();

           

shared_ptr的拷貝和指派

// 拷貝和指派時,對個shared_ptr都會記錄有多少個其他shared_ptr指向相同的對象
auto p = make_shared<int>(42); // p指向的對象隻有p一個引用者
auto q(p);  // p和q指向相同對象,此對象有兩個引用者
// 我們可以認為每個shared_ptr都有一個關聯的電腦,通常稱為 引用計數 reference count
// 每當拷貝一個shared_otr,計數器都會遞增。例如當用一個shared)ptr初始化另一個shared_ptr。
// 當shared_ptr賦予一個新值或是shared_ptr被銷毀(例如一個局部的shared_ptr離開其作用域),電腦就會遞減。為0時,自動釋放管理的對象
           

shared_ptr自動銷毀所管理的對象

當指向一個對象的最後一個shared_ptr被銷毀時,shared_ptr類就會自動銷毀此對象。(通過成員函數:析構函數)

shared_ptr在無用之後仍然保留的一種可能情況是,你将shared_ptr存放在一個容器中,随後重排了容器,進而不再需要某些元素。在這種情況下, 你應該確定用erase删除那些不再需要的shared_ptr元素。

之前使用的類中,配置設定的資源都與對應對象生存期一緻。例如每個vector都擁有自己的元素。當拷貝一個vector時,原vector和副本vector中的元素是互相分離的。

vector<string> v1;//空vector
{// 新作用域
    vector<string> v2 ={"a","b","c"};
    v1=v2;
}//v2被銷毀,v1還有三個元素,是原來v2的拷貝。
// 即vector存在,其中元素才會存在。vector都不存在了,其中元素肯定就銷毀了。
           

但某些類配置設定的資源具有與元對象相獨立的生存期。一般而言,如果兩個對象共享底層的資料,當某個對象被銷毀時,我們不能單方面地銷毀底層資料

Blob<string> b1;//空Blob
{// 新作用域
	Blob<string> b2 ={"a","b","c"};
    b1=b2;//
}//b2被銷毀,但b2中的元素不能銷毀。而b1指向最初由b2建立的元素
           

繼續閱讀