*16.5 shared_ptr使用场景、陷阱、性能分析与使用建议
一、std::shared_ptr使用場景
shared_ptr<int> create(int value) {return make_shared<int>(value); //返回一個shared_ptr }shared_ptr<int> myfunc(int value) {shared_ptr<int> ptmp = create(value);return ptmp; //系統會根據ptmp這個局部變量產生一個臨時的shared_ptr對象往回返 }void myfunc_void(int value) {shared_ptr<int> ptmp = create(value);return; //離開作用域后,ptmp會被自動釋放,它所指向的內存也會被自動釋放 }int main() {myfunc_void(12); //如果這塊不用shared_ptr變量來接收myfunc返回的結果,那么從myfunc返回的shared_ptr就會被銷毀。//所指向的對象也會被銷毀。auto p1 = myfunc(10); //我們用了一個變量來接myfunc的返回值,那么myfunc返回的shared_ptr就不會被銷毀,//它所指向的對象也不會被銷毀。return 0; }二、std::shared_ptr使用陷阱分析
<1>慎用裸指針
void proc(shared_ptr<int> value) {return; }int main() {int* p = new int(100); //裸指針cout << p << endl;//proc(p); //語法錯,int *p不能轉換成shared_ptr<int>//shared_ptr<int> p2(p);//proc(p2); //*p = 45; //有兩個shared_ptr,是沒有問題的proc(shared_ptr<int>(p)); // 參數是個臨時的shared_ptr,用一個裸指針顯式的構造;*p = 45; // 出現潛在的不可預料的問題,因為p指向的內存已經被釋放cout << p << endl;return 0; }把一個普通裸指針綁定到一個 shared_ptr 上之后,那么內存管理的責任就交給 shared_ptr了,這個時候你就不應該再用裸指針(內置指針)來訪問 shared_ptr 所指向的內存了。
void proc(shared_ptr<int> value) {return; }int main() {shared_ptr<int> p3(new int(100));proc(p3);*p3 = 45;return 0; }通過內存查看窗口顯示地址在函數退出時已經釋放,再次使用是不安全的。
絕對要記住不要用裸指針初始化多個shared_ptr
int main() {int* p = new int(100); //裸指針shared_ptr<int> p1(p);shared_ptr<int> p2(p); //p1和p2無關聯(p1和p2的每個強引用計數都為1了),會導致p1和p2所指向的內存釋放兩次,產生異常。shared_ptr<int> p1(new int);shared_ptr<int> p2(p1); //這種寫法就是OK的,p1和p2指向同一個內存地址并且兩者是互通的(用的是同一個控制塊);return 0; }<2>慎用get()返回的指針
返回智能指針指向的對象所對應的裸指針(有些函數接口可能只能使用裸指針)
get返回的指針不能delete,否則會異常。
不能將其他智能指針綁到get返回的指針上!!!
不能將get返回的指針綁到其他智能指針上!!!
結論:永遠不要用get得到的一個指針初始化另一個智能指針或者給另外一個智能指針賦值。
<3>不要用類對象指針( this )作為 shared_ptr 返回,改用 enable_shared_form_this。
class CT { public:shared_ptr<CT> getself(){return shared_ptr<CT>(this); // 用裸指針初始化了多個shared_ptr的感覺} };int main() {shared_ptr<CT> pct1(new CT);shared_ptr<CT> pct2 = pct1; //這是兩個強引用shared_ptr<CT> pct2 = pct1->getself(); //問題出現return 0; }用c++標準庫里面的類模板:enable_shared_from_this。
現在在外面創建CT對象的智能指針以及通過CT 對象返回的 this 智能指針都是安全的
這個 enbale_shared_from_this 中有個弱指針 weak_ptr,這個弱指針能夠監視 this。
在我們調用 shared_from_this() 這個方法時,這個方法內部實際上是調用這個 weak_ptr 的lock() 方法。
大家都知道 lock() 方法會讓 shared_ptr 引用計數+1,同時返回這個 shared_ptr,這個就是工作原理。
<4>避免循環引用:能夠導致內存泄漏 ???析構函數執行順序是依照什么來的???
class CB; //聲明一下CBclass CA { public:shared_ptr<CB> m_pbs;~CA(){int test;test = 1;} };class CB { public://shared_ptr<CA> m_pas;weak_ptr<CA> m_pas; //把這里變成弱引用~CB(){int test;test = 1;} };int main() {shared_ptr<CA> pca(new CA);shared_ptr<CB> pcb(new CB);pca->m_pbs = pcb; //等價于指向CB對象的有兩個強引用pcb->m_pas = pca; //1.等價于指向CA對象的有兩個強引用 2.因為m_pas是弱引用,這里指向CA的對象只有一個強引用return 0; }離開作用域之后,pca引用計數從1變成0會釋放 CA 對象(CA的析構函數被執行)。
CA 的析構函數被執行了(表示對象即將被釋放),導致CA內的 m_pbs 引用計數減1;
也就是指向CB的對象引用計數減1,超出pcb作用域指向CB的引用計數也會減1,最終,會有一個時刻,指向CB對象的強引用計數會從1減少到0,導致CB對象被釋放。CA先析構,CB后析構。
三:性能說明
<1>尺寸問題
shared_ptr的尺寸是裸指針的兩倍, weak_ptr的尺寸也是裸指針的兩倍。
(a)第一個裸指針指向這個智能指針所指向的對象;
(b)第二個裸指針指向一個很大的數據結構(控制塊),這個控制塊里邊有:
(b.1) 所指對象的強引用計數: shared_ptr
(b.2) 所指對象的弱引用計數: weak_ptr
(b.3) 其他數據,比如刪除器指針,內存分配器等等
這個控制塊是由第一個指向某個對象的shared_ptr來創建的
控制塊創建時機
(a)make_shared:分配并初始化一個對象,返回指向此對象的 shared_ptr,所以,這個make_shared 它總是能夠創建一個控制塊
shared_ptr p2 = make_shared(100);
(b)用裸指針來創建一個 shared_ptr 對象時
int* pi = new int();
shared_ptr p1(pi);
shared_ptr p2(pi); // 不允許,否則會產生多個控制塊,也就是多個引用計數(每個都是1),析構時析構多次,導致異常
shared_ptr p2(new int(100));
<2>移動語義
shared_ptr p1(new int(100));
shared_ptr p2(std::move(p1)); // 移動語義,移動構造一個新的智能指針對象p2,p1就不在指向該對象了(變為空),引用計數依舊是1
shared_ptr p3;
p3 = std::move(p2); //移動賦值,p2指向空,p3指向該對象,整個對象引用計數仍舊為1;
移動肯定比復制快,復制你要增加引用計數,移動不需要;
移動構造函數快過復制構造函數,移動賦值運算符快過拷貝賦值運算符
四:補充說明和使用建議
<1>掌握了絕大部分shared_ptr用法, 小部分沒講解,靠大家摸索
分配器解決內存分配問題
shared_ptr p((new int()), mydeleter(), mymallocator());
<2>謹慎使用,凡是老師沒講到過的用法,
new shared_ptr, memcpy() 沒有提及的奇怪用法,不要輕易嘗試
<3>優先使用make_shared(),不能定義自己的刪除器 ???
shared_ptr ps(new string(“I Love China”)); // 分配兩次內存
auto p2 = make_shared(“I Love China”); // 分配一次內存
總結
以上是生活随笔為你收集整理的*16.5 shared_ptr使用场景、陷阱、性能分析与使用建议的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 脱壳基础知识入门
- 下一篇: java读取本地图片并在网页显示