動態內存和智能指針
靜態內存:局部static對象,類static數據成員,定義在任何函數之外的變量
棧內存:保存定義在函數內的非static對象
堆(自由空間):動態分配的對象
頭文件:memory
智能指針,自動的釋放所指向的對象
shared_ptr:允許多個指針指向同一個對象
unique_ptr:獨佔所指向的對象
weak_ptr:一種弱引用,指向shared_ptr所管理的對象
shared_ptr類
shared_ptr<string> p1;
shared_ptr<vector<int>> p2;
使用前進行條件判斷,保證p1不是指針,p1指向了一個空string
shared_ptr<string> p1; if(p1 && p1 -> empty()) { *p1 = "hello"; }
shared_ptr和unique_ptr都支持的操作
p -> mem; //獲取內部成員 p.get(); //返回p中保存的指針,若智能指針釋放了其對象,返回的指針所指向的對象也就消失了 swap(p,q); p.swap(q) //交換p,q
shared_ptr獨有的操作
make_shared<T>(args); //返回一個shared_ptr,指向一個動態分配的類型爲T的對象,用args初始化它 shared_ptr<T>p(q); //p是shared_ptr q的拷貝,會增加q中的計數器 p = q; //保證指針能夠相互轉換,此操作會遞減p的引用計數,遞增q的引用計數;p的引用計數爲0會被刪除 p.unique(); //p.use_count() == 1 p.use_count(); //返回與p共享對象的智能指針數量,很慢,用於調試
通常使用make_shared函數分配使用,若沒傳遞任何參數,會默認進行值初始化
拷貝和賦值的過程本質上都是計數器調整的
shared_prt會調用析構函數來銷燬對象
可以使用函數返回shared_ptr類型,return語句會向此函數的調用者返回一個拷貝,此時原對象隨着函數的銷燬而被銷燬,但此時引用計數值並不爲零,所以對應內存還會保留。
shared_ptr<int> shaptr_test(int x) { auto t = make_shared<int>(x); return t; } int main() { shared_ptr<int> p1 = shaptr_test(5); cout << *p1 << endl; }
所以,注意要保證在無用之後沒有保留,否則會浪費內存。尤其容易忽略的是,把shared_ptr保存在vector中,對vector重排後,忘記erase掉無用元素。
使用動態內存的原因
(1)程序不知道自己需要使用多少對象:vector
(2)程序不知道所需對象的準確類型
(3)程序需要在多個對象間共享內存
在類中定義share_ptr成員,實現多個對象間的共享內存
直接管理內存
運算符new, delete
int *i = new int(1024); int *s = new string(5, '9'); vector<int> *v = new vector<int>{0,1,2}; int *i2 = new int(); //值初始化,*i2爲0 int *i3 = new int; //默認初始化,*i3的值未定義
對於定義了自己的構造函數的類,值初始化都會通過默認的構造函數來初始化;對於內置類型纔會有區別。
使用auto自動判斷
auto p1 = new auto(obj); //p1指向一個與obj類型相同的對象,使用obj的值進行初始化
用new分配const對象
const int *p = new const int(10);
雖然不能改變const動態對象,但可以用delete來銷燬
對於定義了默認構造函數的類類型,其const對象可以隱式初始化,其它則必須顯式初始化
內存耗盡時,new會拋出std::bad_alloc異常
使用定位new傳遞額外參數nothrow,當分配失敗時,返回一個空指針
int *p = new (nothow) int;
delete之後最好重置指針
在delete之後,很多機器上指針仍然保存着(已經釋放了的)動態內存的地址,此時指針變成了空懸指針。所以,在指針要離開作用域前要釋放它關聯的內存,如果要保留指針,可以在delete之後重置爲nullptr
只是有限的保護,若有多個指針指向同一地址則很難檢測到。
new與shared_ptr結合使用
可以用new初始化shared_ptr
shared_ptr<int> p(new int(35));
接受指針參數的智能指針構造函數是explicit的,故不能講一個內置指針隱式轉換爲智能指針,必須通過顯式的綁定
定義和改變shared_ptr的方法
tr《T
shared_ptr<T> p(q); //p管理內置指針q所指向的對象,q必須指向new分配的內存,且能夠轉換爲T*類型 shared_ptr<T> p(u); //p從unique_ptr u處接管對象的所有權,u被置空 shared_ptr<T> p(q, d); //p接管了q所指向對象的所有權,使用可調用對象d代替delete shared_ptr<T> p(p2,d); //p是shared_ptr p2的拷貝,區別是p將用可調用對象d來代替delete p.reset(); p.reset(q); p.reset(q, d); //若p是唯一指向對象的shared_ptr,reset會釋放,或另p指向q,參數d用於提供delete操作
普通指針和智能指針混合使用易產生的錯誤
void process(shared_ptr<int> ptr) { /*……*/ } /*……*/ int *p(new int(100)); process(shared_ptr<int>(p)); int j = *p;
將臨時的shared_ptr傳入到process後,當調用結束時,臨時對象將被銷燬,引用次數變爲0,p變成空懸指針,此時使用p的行爲是未定義的。
當將一個shared_ptr綁定到一個普通指針時,相當於管理責任的移交,最好不要再使用內置指針。
同樣的,shared_ptr定義了一個get函數,返回一個內置指針,應用場景是:向不能使用智能指針的代碼傳遞一個內置指針,這裏需要注意不要被delete。特別是,永遠不要用get初始化另一個智能指針。
在改變底層對象之前,檢查自己是否是當前對象僅有的用戶
if(!p.unique()) q.reset(new int(*p)); *p += val;
智能指針和異常
使用智能指針,即使程序塊由於異常而過早結束,也能確保在內存不再需要時將其釋放。
內置指針則做不到。
對於那些沒有定義析構函數的類,通常要求用戶顯式地釋放所使用的任何資源,這時如果使用shared_ptr來避免內存泄露會默認的使用delete。但對於有些資源並不適用,比如網絡連接,這時可以採用自定義的函數來代替delete。
void end_connection(connection *p) { disconnect(*p); } /* ……*/ void f(destination &d) { connection c = connect(&d); shared_ptr<connection> p (&c, end_connection); /* ……*/ }
此時,即便是由於異常而退出,也可以保證connection會被正確光比
unique_ptr
定義
只能將其綁定到一個new返回的指針上
unique_ptr<int> p1; unique_ptr<int> p2(new int(10));
unique_ptr的對象是獨有的,所以不支持普通的拷貝或賦值。
//使用類型爲D的可調用對象d代替delete unique_ptr<T> u1; unique_ptr<T, D> u2; unique_ptr<T, D> u(d); u = nullptr; //釋放並置空 u.release(); //放棄控制權,返回當前保存的指針,置空 //釋放原對象,將u指向q的對象 u.reset(); u.reset(q); u.reset(nullptr);
僅使用release不會釋放內存,而且會丟失原指針,如:p.release()
傳遞和返回unique_ptr
不能拷貝unique_ptr的規則有一個例外:可以拷貝或賦值一個將要被銷燬的unique_ptr。
典型的例子是從函數返回一個unique_ptr
auto_ptr
auto_ptr有拷貝語義,拷貝後源對象變得無效;unique_ptr則無拷貝語義,但提供了移動語義
auto_ptr不可作爲容器元素,unique_ptr可以作爲容器元素
auto_ptr不可指向動態數組(儘管不會報錯,但不會表現出正確行爲),unique_ptr可以指向動態數組
weak_ptr
不控制所指向對象生存期的智能指針,指向一個由shared_ptr管理的對象,不改變shared_ptr的引用計數,一旦shared_ptr被銷燬,weak_ptr沒有任何阻礙的效果。
使用方法
weak_ptr<T> w; weak_ptr<T> w(sp); //與shared_ptr sp指向相同對象 w = p; w.reset(); //w置空 w.use_count(); w.expired(); //w.use_count() == 0 w.lock(); //若expired爲true,返回空shared_ptr,否則返回一個指向w對象的shared_ptr
訪問前注意判定
if(shared_ptr<int> sp = w.lock()) { /*……*/ }
動態數組
一次爲很多對象分配內存
new表達式,allocator類
更多的情況還是使用容器
new和數組
int *p = new int[get_size()]; //p指向第一個int
方括號中的大小必須是整型,但不必是常量
由於分配的內存並不是一個數組類型,因此不能對動態數組調用begin或end
關於初始化
int *pi = new int[10]; //10個未初始化 int *pi = new int[10](); //10個值初始化
還可以使用列表初始化,不能用auto分配數組
有趣的是,可以動態分配一個空數組,使用時除了不能解引用可以進行其它操作,類似於一個尾後迭代器
char arr[0]; //錯誤 char *cp = new char[0]; //正確,但不能解引用
釋放數組
delete [] arr;
即使是使用別名來定義一個數組,釋放時也必須加上[],但是在vs下測試可以不加上[]
typedef int arr[10]; //arr爲10個int的數組的類型別名 int *p = new arr(); delete []p; //方括號理論上必須
順序是按照駐足中元素的逆序
智能指針和動態數組
可以管理new分配的數組的unique_ptr版本
unique_ptr<int[]> up_arr(new int[10]); up_arr.release(); //自動用delete[]銷燬指針
可以使用下標直接訪問
如果希望使用shared_ptr管理一個動態數組,必須提供一個刪除器
shared_ptr<int[]> up_arr(new int[10](), [](int *p) {delete[] p;}); up_arr.reset();
傳遞給一個lambda作爲刪除器。若未提供刪除器,默認情況shared_ptr使用delete來銷燬對象,這與釋放動態數組時沒有加[]產生的問題一樣。
shared_ptr未定義下標運算符,且不支持指針的算術運算,所以訪問只能通過get之後再加減(vs下不支持)
allocator類
將內存分配和構造對象分離,避免浪費。
頭文件memory
是一個模板,分配的內存是原始的、未構造的。
基本操作
allocator<T> a; a.allocate(n); //分配內存,保存n個類型爲T的對象,返回起始位置指針 //釋放從指針p出開始的內存,共保存了n個對象,p必須是先前由allocate返回的指針,n必須爲當初構造時的大小 a.deallocate(p, n); //args被傳遞給T的構造函數,用於在p指向的內存中構造一個對象 a.construct(p ,args); a.destory(p); //對p指向的對象析構
分配內存
allocator<string> alloc; auto p = alloc.allocate(10); auto q = p; alloc.construct(p++); //空字符串 alloc.construct(p++, 5, 'h'); //5個h alloc.construct(p++, 'hello');
早期版本中不能採用其它構造函數來構造一個元素,只有兩個參數:指針,參數類型的值
逐個destory,但內存並沒有被釋放
while(q != p) { alloc.destory(--p); }
拷貝和填充
uninitialized_copy(b, e, b2); //從迭代器b和e間拷貝元素到迭代器b2指定的未構造的原始內存中,假定b2足夠大 uninitialized_copy(b, n, b2); //拷貝n個元素uninitialized_fill(b, e, t); //在迭代器b和e指定的原始內存範圍中創建對象,值爲t的拷貝 uninitialized_fill(b, n, t); //拷貝n個,假定足夠大