深入學(xué)習(xí)C++智能指針之shared_ptr與右值引用的方法
1. 介紹
在 C++ 中沒(méi)有垃圾回收機(jī)制,必須自己釋放分配的內(nèi)存,否則就會(huì)造成內(nèi)存泄露。解決這個(gè)問(wèn)題最有效的方法是使用智能指針(smart pointer)。智能指針是存儲(chǔ)指向動(dòng)態(tài)分配(堆)對(duì)象指針的類(lèi),用于生存期的控制,能夠確保在離開(kāi)指針?biāo)谧饔糜驎r(shí),自動(dòng)地銷(xiāo)毀動(dòng)態(tài)分配的對(duì)象,防止內(nèi)存泄露。智能指針的核心實(shí)現(xiàn)技術(shù)是引用計(jì)數(shù),每使用它一次,內(nèi)部引用計(jì)數(shù)加1,每析構(gòu)一次內(nèi)部的引用計(jì)數(shù)減1,減為0時(shí),刪除所指向的堆內(nèi)存。
C++11 中提供了三種智能指針,使用這些智能指針時(shí)需要引用頭文件
- std::shared_ptr:共享的智能指針
- std::unique_ptr:獨(dú)占的智能指針
- std::weak_ptr:弱引用的智能指針,它不共享指針,不能操作資源,是用來(lái)監(jiān)視 shared_ptr 的。
共享智能指針(shared_ptr)是指多個(gè)智能指針可以同時(shí)管理同一塊有效的內(nèi)存,共享智能指針 shared_ptr 是一個(gè)模板類(lèi),如果要進(jìn)行初始化有三種方式:通過(guò)構(gòu)造函數(shù)、std::make_shared 輔助函數(shù)以及 reset 方法。共享智能指針對(duì)象初始化完畢之后就指向了要管理的那塊堆內(nèi)存,如果想要查看當(dāng)前有多少個(gè)智能指針同時(shí)管理著這塊內(nèi)存可以使用共享智能指針提供的一個(gè)成員函數(shù) use_count
2. 初始化方法
2.1 通過(guò)構(gòu)造函數(shù)初始化
實(shí)例
// 使用智能指針管理一塊 int 型的堆內(nèi)存 shared_ptr<int> ptr1(new int(520));
2.2 通過(guò)拷貝和移動(dòng)構(gòu)造函數(shù)初始化
調(diào)用拷貝構(gòu)造函數(shù)
shared_ptr<int> ptr2(ptr1);
調(diào)用移動(dòng)構(gòu)造函數(shù)
std::shared_ptr<int> ptr5 = std::move(ptr2);
如果使用拷貝的方式初始化共享智能指針對(duì)象,這兩個(gè)對(duì)象會(huì)同時(shí)管理同一塊堆內(nèi)存,堆內(nèi)存對(duì)應(yīng)的引用計(jì)數(shù)也會(huì)增加;
如果使用移動(dòng)的方式初始智能指針對(duì)象,只是轉(zhuǎn)讓了內(nèi)存的所有權(quán),管理內(nèi)存的對(duì)象并不會(huì)增加,因此內(nèi)存的引用計(jì)數(shù)不會(huì)變化。
2.2.1 移動(dòng)構(gòu)造
關(guān)于移動(dòng)構(gòu)造,可能有些讀者不太明白
移動(dòng)構(gòu)造是C++11標(biāo)準(zhǔn)中提供的一種新的構(gòu)造方法。
在現(xiàn)實(shí)中有很多這樣的例子,我們將錢(qián)從一個(gè)賬號(hào)轉(zhuǎn)移到另一個(gè)賬號(hào),將手機(jī)SIM卡轉(zhuǎn)移到另一臺(tái)手機(jī),將文件從一個(gè)位置剪切到另一個(gè)位置……移動(dòng)構(gòu)造可以減少不必要的復(fù)制,帶來(lái)性能上的提升。
我們首先來(lái)看看move函數(shù)
首先看這樣一段代碼
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
using namespace std;
int main()
{
string st = "I love 進(jìn)擊的汪sir";
vector<string> vc;
vc.push_back(move(st));
cout << vc[0] << endl;
if (!st.empty())
cout << st << endl;
return 0;
}
輸出的結(jié)果為

再看這樣一段代碼
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
using namespace std;
int main()
{
string st = "I love xing";
vector<string> vc;
vc.push_back(st);
cout << vc[0] << endl;
if (!st.empty())
cout << st << endl;
return 0;
}
其結(jié)果為

這兩段代碼唯一的不同是調(diào)用vc.push_back()將字符串插入到容器中去時(shí),第一段代碼使用了move語(yǔ)句,而第二段代碼沒(méi)有使用move語(yǔ)句。輸出的結(jié)果差異也很明顯,第一段代碼中,原來(lái)的字符串st已經(jīng)為空,而第二段代碼中,原來(lái)的字符串st的內(nèi)容沒(méi)有變化。
先暫時(shí)記住這兩端代碼的輸出結(jié)果之間的差異。
我們回到移動(dòng)構(gòu)造函數(shù)上
有時(shí)候我們會(huì)遇到這樣一種情況,我們用對(duì)象a初始化對(duì)象b,后對(duì)象a我們就不在使用了,但是對(duì)象a的空間還在呀(在析構(gòu)之前),既然拷貝構(gòu)造函數(shù),實(shí)際上就是把a(bǔ)對(duì)象的內(nèi)容復(fù)制一份到b中,那么為什么我們不能直接使用a的空間呢?這樣就避免了新的空間的分配,大大降低了構(gòu)造的成本。這就是移動(dòng)構(gòu)造函數(shù)設(shè)計(jì)的初衷。
通俗一點(diǎn)的解釋就是,拷貝構(gòu)造函數(shù)中,對(duì)于指針,我們一定要采用深層復(fù)制,而移動(dòng)構(gòu)造函數(shù)中,對(duì)于指針,我們采用淺層復(fù)制。
所以在上面的例子中,如果調(diào)用移動(dòng)構(gòu)造函數(shù)來(lái)初始化智能指針,引用計(jì)數(shù)是不會(huì)增加的,而move函數(shù)實(shí)際上是返回的右值引用
2.2.2 右值引用
上面我們講到了右值引用,這里就來(lái)擴(kuò)展一下右值引用是啥
首先得分清楚,什么是右值,什么是左值
- lvalue 是 loactor value 的縮寫(xiě),rvalue 是 read value 的縮寫(xiě)
- 左值是指存儲(chǔ)在內(nèi)存中、有明確存儲(chǔ)地址(可取地址)的數(shù)據(jù);
- 右值是指可以提供數(shù)據(jù)值的數(shù)據(jù)(不可取地址);
通過(guò)描述可以看出,區(qū)分左值與右值的便捷方法是:可以對(duì)表達(dá)式取地址(&)就是左值,否則為右值 。所有有名字的變量或?qū)ο蠖际亲笾?,而右值是匿名的?/p>
C++11 中右值可以分為兩種:一個(gè)是將亡值( xvalue, expiring value),另一個(gè)則是純右值( prvalue, PureRvalue):
- 純右值:非引用返回的臨時(shí)變量、運(yùn)算表達(dá)式產(chǎn)生的臨時(shí)變量、原始字面量和 lambda 表達(dá)式等
- 將亡值:與右值引用相關(guān)的表達(dá)式,比如,T&& 類(lèi)型函數(shù)的返回值、 std::move 的返回值等。
右值引用就是對(duì)一個(gè)右值進(jìn)行引用的類(lèi)型。因?yàn)橛抑凳悄涿模晕覀冎荒芡ㄟ^(guò)引用的方式找到它。無(wú)論聲明左值引用還是右值引用都必須立即進(jìn)行初始化,因?yàn)橐妙?lèi)型本身并不擁有所綁定對(duì)象的內(nèi)存,只是該對(duì)象的一個(gè)別名。通過(guò)右值引用的聲明,該右值又“重獲新生”,其生命周期與右值引用類(lèi)型變量的生命周期一樣,只要該變量還活著,該右值臨時(shí)量將會(huì)一直存活下去。
右值通過(guò)&&來(lái)引用
例如:
int&& value = 520;里面 520 是純右值,value 是對(duì)字面量 520 這個(gè)右值的引用。int &&a2 = a1;中 a1 雖然寫(xiě)在了 = 右邊,但是它仍然是一個(gè)左值,使用左值初始化一個(gè)右值引用類(lèi)型是不合法的。const Test& t = getObj()這句代碼的語(yǔ)法是正確的,常量左值引用是一個(gè)萬(wàn)能引用類(lèi)型,它可以接受左值、右值、常量左值和常量右值。
2.3 通過(guò) std::make_shared 初始化
通過(guò) C++ 提供的 std::make_shared() 就可以完成內(nèi)存對(duì)象的創(chuàng)建并將其初始化給智能指針,函數(shù)原型如下:
template< class T, class... Args > shared_ptr<T> make_shared( Args&&... args );
實(shí)例
使用智能指針管理一塊 int 型的堆內(nèi)存, 內(nèi)部引用計(jì)數(shù)為 1
shared_ptr<int> ptr1 = make_shared<int>(520);
注意
使用 std::make_shared() 模板函數(shù)可以完成內(nèi)存地址的創(chuàng)建,并將最終得到的內(nèi)存地址傳遞給共享智能指針對(duì)象管理。如果申請(qǐng)的內(nèi)存是普通類(lèi)型,通過(guò)函數(shù)的()可完成地址的初始化,如果要?jiǎng)?chuàng)建一個(gè)類(lèi)對(duì)象,函數(shù)的()內(nèi)部需要指定構(gòu)造對(duì)象需要的參數(shù),也就是類(lèi)構(gòu)造函數(shù)的參數(shù)。
2.4 通過(guò) reset 方法初始化
共享智能指針類(lèi)提供的 std::shared_ptr::reset 方法函數(shù)原型如下:
void reset() noexcept; template< class Y > void reset( Y* ptr ); template< class Y, class Deleter > void reset( Y* ptr, Deleter d ); template< class Y, class Deleter, class Alloc > void reset( Y* ptr, Deleter d, Alloc alloc );
- ptr:指向要取得所有權(quán)的對(duì)象的指針
- d:指向要取得所有權(quán)的對(duì)象的指針
- aloc:內(nèi)部存儲(chǔ)所用的分配器
實(shí)例
shared_ptr<int> ptr5; ptr5.reset(new int(250));
3. 獲取原始指針
對(duì)應(yīng)基礎(chǔ)數(shù)據(jù)類(lèi)型來(lái)說(shuō),通過(guò)操作智能指針和操作智能指針管理的內(nèi)存效果是一樣的,可以直接完成數(shù)據(jù)的讀寫(xiě)。但是如果共享智能指針管理的是一個(gè)對(duì)象,那么就需要取出原始內(nèi)存的地址再操作,可以調(diào)用共享智能指針類(lèi)提供的 get () 方法得到原始地址,其函數(shù)原型如下:
T* get() const noexcept;
實(shí)例
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
int len = 128;
shared_ptr<char> ptr(new char[len]);
// 得到指針的原始地址
char* add = ptr.get();
memset(add, 0, len);
strcpy(add, "博客:進(jìn)擊的汪sir");
cout << "string: " << add << endl;
shared_ptr<int> p(new int);
*p = 100;
cout << *p.get() << " " << *p << endl;
return 0;
}
4. 指定刪除器
當(dāng)智能指針管理的內(nèi)存對(duì)應(yīng)的引用計(jì)數(shù)變?yōu)?0 的時(shí)候,這塊內(nèi)存就會(huì)被智能指針析構(gòu)掉了。另外,我們?cè)诔跏蓟悄苤羔樀臅r(shí)候也可以自己指定刪除動(dòng)作,這個(gè)刪除操作對(duì)應(yīng)的函數(shù)被稱(chēng)之為刪除器,這個(gè)刪除器函數(shù)本質(zhì)是一個(gè)回調(diào)函數(shù),我們只需要進(jìn)行實(shí)現(xiàn),其調(diào)用是由智能指針完成的。
實(shí)例
#include <iostream>
#include <memory>
using namespace std;
// 自定義刪除器函數(shù),釋放int型內(nèi)存
void deleteIntPtr(int* p)
{
delete p;
cout << "int 型內(nèi)存被釋放了...";
}
int main()
{
shared_ptr<int> ptr(new int(250), deleteIntPtr);
return 0;
}
刪除器函數(shù)也可以是 lambda 表達(dá)式!
5. 參考鏈接
https://subingwen.cn/cpp/shared_ptr/
https://www.cnblogs.com/qingergege/p/7607089.html
到此這篇關(guān)于C++智能指針之shared_ptr與右值引用的文章就介紹到這了,更多相關(guān)C++智能指針內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++特性之智能指針shared_ptr詳解
- C++ Boost shared_ptr共享指針詳細(xì)講解
- C++簡(jiǎn)單實(shí)現(xiàn)shared_ptr的代碼
- C++中Boost的智能指針shared_ptr
- C++智能指針之shared_ptr的具體使用
- C++智能指針之shared_ptr詳解
- C++智能指針shared_ptr
- C++11中的智能指針shared_ptr、weak_ptr源碼解析
- C++11 std::shared_ptr總結(jié)與使用示例代碼詳解
- C++11 智能指針之shared_ptr代碼詳解
- C++共享智能指針shared_ptr的實(shí)現(xiàn)
相關(guān)文章
C++實(shí)現(xiàn)線性表順序存儲(chǔ)的示例代碼
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)線性表順序存儲(chǔ)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下2023-03-03
C語(yǔ)言詳解無(wú)頭單向非循環(huán)鏈表各種操作方法
無(wú)頭單向非循環(huán)鏈表:結(jié)構(gòu)簡(jiǎn)單,一般不會(huì)單獨(dú)用來(lái)存數(shù)據(jù)。實(shí)際中更多是作為其他數(shù)據(jù)結(jié)構(gòu)的子結(jié)構(gòu),如哈希桶、圖的鄰接表等等。另外這種結(jié)構(gòu)在筆試面試中出現(xiàn)很多2022-04-04
C語(yǔ)言編程中實(shí)現(xiàn)二分查找的簡(jiǎn)單入門(mén)實(shí)例
這篇文章主要介紹了C語(yǔ)言編程中實(shí)現(xiàn)二分查找的簡(jiǎn)單入門(mén)實(shí)例,需要的朋友可以參考下2015-12-12
C語(yǔ)言如何求整數(shù)的位數(shù)及各位數(shù)字之和
這篇文章主要介紹了C語(yǔ)言如何求整數(shù)的位數(shù)及各位數(shù)字之和,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
C語(yǔ)言實(shí)現(xiàn)打飛機(jī)小游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的打飛機(jī)小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02
C++20中的結(jié)構(gòu)化綁定類(lèi)型示例詳解
這篇文章主要為大家介紹了C++20中的結(jié)構(gòu)化綁定類(lèi)型示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
C語(yǔ)言實(shí)現(xiàn)魔方陣算法(幻方陣 奇魔方 單偶魔方實(shí)現(xiàn))
魔方陣是指由1,2,3……n2填充的,每一行、每一列、對(duì)角線之和均相等的方陣,階數(shù)n = 3,4,5…。魔方陣也稱(chēng)為幻方陣,看下面的實(shí)現(xiàn)方法吧2013-11-11

