要點彙總:1) #9 - 基類的析構函數必須是virtual,否則可能導緻析構調用鍊斷層。
智能指針的使用:1)使用 普通指針/其他已存在的智能指針/其他已存在的普通指針,對目前建立的智能指針進行初始化。 (建立 指針 指向目前已有記憶體)
2)使用 make_shared 建立全新的記憶體區,然後建立一個全新的智能指針指向它。 (建立 記憶體空間 和 指針)
3)使用 智能指針的 reset 方法來進行 智能指針的 重定向 和 釋放 (重定向 和 銷毀) #1 ===========================================================================================================================
智能指針智能指針有兩種: shared_ptr 和 unique_ptr
這兩種指針都是模闆類,是以原型為 xxx_ptr<T>
(!)注:智能指針是用來管理堆記憶體的,不是作為指針使用的
(!)智能指針是使用delete來釋放記憶體,是以釋放記憶體時的特性和delete一樣
#2 ===========================================================================================================================
(!)首先明确一點,智能指針“僅僅”用來“動态配置設定記憶體”,而不是用來當做指針使用!!!!
是以:
int i = 100; //或者其他類,比如 classA a;
shared_ptr<int> sp = make_shared<int>(i);上述語句實際上是使用i的值作為新配置設定記憶體的初始值。sp并沒有指向i,而是指向了新配置設定的堆,這個堆存放int型的數值100
上述語句不能算錯誤,但是需要了解 make_shared 動作實際上是取堆裡申請了記憶體的。
(可以這樣了解,make_shared相當于 new ,尖括号指定需要配置設定記憶體的類型名,小括号指定作為拷貝構造傳遞給
類型名的值,如果不指定,那麼使用類型的預設構造) 同理:
classA *pa = new classA();
shared_ptr<classA> sp = make_shred<classA>(*pa); //使用classA的拷貝構造在堆裡建立一個新的對象,而不是使用pa指向的對象 ====== EXAMPLE 1 =======
#include "stdafx.h"
#include <iostream>
#include <memory>
using namespace std;class A{
public:
A() = default;
~A(){ ; }
public:
int myint=10;
};int _tmain(int argc, _TCHAR* argv[])
{
A* pa = new A();
shared_ptr<A> sp = make_shared<A>(*pa);
sp->myint = 100; cout << pa->myint << endl; //pa = 10 ,值并沒有改變,因為它是用 new 配置設定的堆
cout << sp->myint << endl; //sp = 100,值和pa不一樣,因為它是用 make_shared 配置設定的堆 getchar();
return 0;
} 注:小括号裡的内容必須與尖括号裡類型的某個構造函數相比對
====== EXAMPLE 2 ======
#include "stdafx.h"
#include <iostream>
#include <memory>
using namespace std;class A{
public:
A() = default;
~A(){ ; }
public:
int myint=10;
};int _tmain(int argc, _TCHAR* argv[])
{
shared_ptr<A> sp = make_shared<A>(); //使用 A 的預設構造函數 cout << sp.use_count() << endl; //為1
shared_ptr<A> sp1 = sp; //增加了sp指向記憶體區的計數
cout << sp.use_count() << endl; //為2
cout << sp1.use_count() << endl; //為2 sp1 = NULL;
cout << sp.use_count() << endl; //為1
getchar();
//小結:可見智能共享指針的計數器是由系統自動管理的,具體内部實作可能是 靜态資料類型,這樣各個
// 執行個體操作的就是同一個資料,當然,可能會涉及到線程安全的問題,這時候就要加鎖,這也伴随着
// 效率略低和死鎖的風向 return 0;
}#3 ===========================================================================================================================
(!)在使用共享指針時,我們隻需要操作指針對象,不要試圖去直接操作指針指向的内容。因為當所有指針都銷毀的時候,動态配置設定的記憶體會被
自動釋放。 計數增加:
1)初始化一個shared_ptr
2)作為參數傳遞給一個函數
3)作為函數傳回值計數減少:
1)給shared_ptr新指向對象,替換舊對象,那麼就對象計數減少
2)shared_ptr調用析構函數,比如局部變量離開作用域等等 小結:shared_ptr将對對象的記憶體管理轉變為對對指向對象指針的管理。而這個指針的獲得僅能通過shared_make來實作。是以,從
某種意義上來說,更集中。
#4 ===========================================================================================================================
共享指針的應用場景:
1)程式不知道自己需要多少個對象???
2)程式不知道所需要對象的準确類型???
3)程式需要在多個對象之間共享資料,這有點像類的static成員變量,是所有類共享的。但是既然有static,為什麼還要共享指針??? (!!!)使用動态記憶體的一個常見原因是:允許多個對象共享相同的狀态。
(!!!)為什麼不做成static的?
主要因為static資料存放在.bss區,無法手動釋放,在程式運作時就載入,而這部分的可用空間是有限而狹小的。如果我們想要使用堆棧這樣
寬敞的空間來模拟static成員,此時就可以使用共享指針管理的動态記憶體(堆)來實作,實作一個僅在大家都不在需要時才會釋放的堆區域。 #5 ===========================================================================================================================
引用計數法的内部實作:
1)這個引用計數器儲存在某個内部類型中,而這個内部類型對象在shared_ptr第一次構造時以指針的形式儲存在shared_ptr中
2)shared_ptr重載了指派運算符,在指派和拷貝另一個shared_ptr時,這個指針被另一個shared_ptr共享
3)在引用計數歸0時,這個内部類型指針與shared_ptr管理的資源一起釋放
* 4)此外,為了保證線程安全,引用計數器的加1和減1都是原子操作,它保證了shared_ptr由多個線程共享時不會爆掉
#6 ===========================================================================================================================
一個典型的場景:
class A{
public:
vector<classB> m_B_list; //非常龐大的一個清單(實際這裡可以使vector指針,這裡隻是做一個描述)}
如果A有N多個執行個體,問題就出現了,每個A執行個體都要有一個m_B_list,首先很吃記憶體,其次各個A執行個體之間需要實施互相同步資料,進而保證這個
vector對于所有A來說,都是一樣的。這是很棘手和難處理的:
1)可以讓vector是static的,但是這樣比較占用static區域,而且無法釋放 (不可選)
2)可以簡單粗暴,讓所有A在更改vector之後,通知其他A跟新自己的vector,這樣既 占記憶體,又 占CPU (不可選)
3)建立一個command管理類,讓command管理類來存放vector,并提供增删改查接口給所有A使用,這樣保證了效率,一定程度上節約了記憶體。
但是,需要新增一個類,而這個類的存在感很低,因為它隻是來做一個中轉 (不是最優解)
4)不建立command管理類,但是在外部區域建立vector,然後讓A都能通路,這樣有風險,如果某個A出于“某種原因”把vector給删掉了。
那麼其他A在通路的時候将通路到空指針,或者直接崩潰。這種情況特别是在A都存放vector指針是明顯,因為析構時會釋放成員變量。(不是最優解)*5)使用shared_ptr,vector不使用new建立,而是使用shared_make,在A構造的時候建立。但是需要注意各個A運作在不同的線程下,同時
操作vector可能産生競争。 第一種實作為 =============
---A.h---
class A{
public:
A();
A(shared_ptr<vector<classB>> tmplist); public:
shared_ptr<vector<classB>> m_p_B_list; }
---A.cpp---
A::A():m_p_B_list(make_shared<vector<classB>>()){ //預設構造,清單初始化 }
A::A(shared_ptr<vector<classB>> tmplist):m_p_B_list(tmplist){ }
(???)上面的實作雖然使用了共享指針管理動态記憶體,但是多個A如果都使用預設構造的話,實際上還是建立了多個vector執行個體,然後各個A
管理自己的vector #7 ===========================================================================================================================
動态配置設定const對象是合法的,new const string("xxxx");
按照const對象的屬性,const必須在定義是初始化,是以這種情況下不能使用預設構造函數。也可以通過delete來釋放配置設定的記憶體(???)疑問:const類型變量一般存放在 .data段 。那麼動态配置設定的常量是否 也在其中。如果不是,是否存放在堆内,如果是在堆内,那麼這塊
區域是如何增加讀寫權限的,畢竟const類型隻讀不可寫。 #8 ===========================================================================================================================
記憶體耗盡:
int *p = new (nothrow) int; //如果記憶體耗盡,那麼不抛異常bad_alloc,隻是傳回空指針注:預設情況下,c++中,記憶體耗盡會抛出異常bad_alloc
#9 ===========================================================================================================================
delete接收一個指針,指針要麼指向一個對象,要麼是空指針。
delete動作會執行以下兩步:1)調用指針指向對象的析構函數;
2)釋放指針指向的記憶體。 如果指針類型和指向的内容不一緻,比如 void* p = new classA();
==== 例子 ====
class A{
public:
A(){}
~A(){ cout << "~A()"; }
};class B :public A{
public:
B(){}
~B(){ cout << "~B()"; }
}; int _tmain(int argc, _TCHAR* argv[])
{
//A *p_A = new A();
void *p_B = new B(); //delete p_A;
delete p_B; getchar();
return 0;
} 小結:1)delete隻認指針類型,如果指針是void類型,“那麼不會調用任何析構函數”。
2)同理,如果有父子關系,那麼delete父類指針,“隻會調用父類的析構函數”。
(*) 3)為了避免1)和2)中的問題,基類的析構函數必須是 “virtual” 的。
4)不論delete接收的是子類的指針還是基類的指針。類執行個體都會被完整地釋放掉,delete是跟着執行個體走,隻認位址,而一旦涉及
到位址部分,malloc和free便不再理會指針類型,而是從作業系統層面完成記憶體的釋放。 (!!!)注:上面說到了 “基類的析構函數必須是virtual,否則在delete指向子類的基類指針時,會出現子類析構不會
被調用的問題”。如果子類中沒有需要回收的記憶體(通過new和malloc配置設定的),那麼記憶體是安全的,子類執行個體
會被正确地釋放。但是如果子類中進行了動态記憶體配置設定(new,malloc),那麼這部分記憶體就無法再被回收,至此
會導緻記憶體洩露。
#10 ===========================================================================================================================
野指針:野指針是指,指針指向的記憶體已經被釋放掉了,但是指針沒有置空。使用delete操作指針并不會觸發指針的置空,是以在delete釋放
完記憶體以後,需要手動把指針置空,因為記憶體區域已經不存在了,此時使用指針去通路就會觸發異常 ---> "非法記憶體區通路"。#11 ===========================================================================================================================
可以在delete後立即置空指針來避免野指針的出現。但是如果某塊記憶體區域被多個指針指向,那麼就很容易忽略某個指針的置空操作。
是以:但凡涉及到多個指針指向同一塊記憶體區域的場景,“ 務必使用智能指針 ”來管理記憶體,此時我們不需要調用delete,隻需要管理指針
即可,當所有的指針都置空,則記憶體被釋放。#12 ===========================================================================================================================
上面提到了智能指針的使用方法,通過make_shared來配置設定記憶體,但是如果想使用new來配置設定記憶體,然後交由智能指針管理,該如何操作???
首先,智能指針的構造函數時explict的,即不接受隐式轉換,即必須是指定類型,故先new在通過指派構造是行不通的。
可以使用複制構造函數來實作:
shared_ptr<classA> p_A = new classA(); //錯誤
shared_ptr<classA> p_A(new classA()); //正确同理,使用普通指針給智能指針指派也是行不通的:
shared_ptr<classA> clone(){ //錯誤
return new classA(); //傳回值不接收這種隐式轉換
}shared_ptr<classA> clone(){ //正确
return shared_ptr<classA>(new classA()); //傳回值也是智能指針
} (!!!)注:上面提到的使用普通指針來初始化智能指針的場景,要求普通指針指向的必須是動态記憶體(即new配置設定的),靜态記憶體不行,
比如:
int i = 10;
const int j = 10;
int *p = &i;
const int *pj = &j;
shared_ptr<int> sp(p); //不能把棧給智能指針
shared_ptr<int> spp(pj); //不能把data區給智能指針#(!)13 ===========================================================================================================================
shared_ptr 和 unique_ptr 的構造和析構
classA A;
classA *p_A = &A; //建立一個執行個體給普通指針unique_ptr<classA> up(new A()); //建立一個執行個體給unique智能指針
shared_ptr<classA> sp(new A()); //建立一個執行個體給智能指針shared_ptr<classA> sp1(p_A); //讓智能指針指向p_A(普通指針)指向的對象,要求unique_ptr指向的類型和shared_ptr指向的類型能夠互換
shared_ptr<classA> sp2(up); //讓智能指針指向unique_ptr指向的對象,要求unique_ptr指向的類型和shared_ptr指向的類型能夠互換shared_ptr<classA> sp3(p_A,mydelete) //讓智能指針指向p_A(普通指針)指向的對象,在所有智能指針都釋放以後,不再使用delete釋放記憶體,而是使用mydelete
shared_ptr<classA> sp4(sp1,mydelete) //讓智能指針指向sp1(智能指針),并使用mydelete代替delete來釋放記憶體sp.reset() //把sp從目前指向關系中解放出來,如果sp是最後一個指針,那麼調用delete釋放目标對象
sp.reset(sp1) //把sp重定向到sp1
sp.reset(sp1,mydelete) //把sp中定向到sp1,如果原sp指向的對象已經沒有指針再指向,那麼使用mydelete來釋放原對象 注:如果智能指針指向的不是new出來的動态記憶體,如果需要釋放資源,那麼一定要提供mydelete來替代delete,因為這種情況下不會調用delete
#(!)14 ===========================================================================================================================
(!!!)
智能指針的使用:1)使用 普通指針/其他已存在的智能指針/其他已存在的普通指針,對目前建立的智能指針進行初始化。 (建立 指針 指向目前已有記憶體)
2)使用 make_shared 建立全新的記憶體區,然後建立一個全新的智能指針指向它。 (建立 記憶體空間 和 指針)
3)使用 智能指針的 reset 方法來進行 智能指針的 重定向 和 釋放 (重定向 和 銷毀)#15 ===========================================================================================================================
weak_ptr
weak_ptr 是給 shared_ptr做補充的,把weak_ptr增加到shared_ptr指向的對象上,不會增加計數,單當shared_ptr全部釋放完以後,
weak_ptr指向的内容也将不存在。 weak_ptr的使用必須 經過自己的lock()方法,這個方法會peek對象是否存在,存在傳回true,否則
false,用完釋放weak_ptr亦不會導緻計數減少。weak_prt可以了解為shared_ptr的螢幕。僅僅用來peek資料。