網站首頁 編程語言 正文
我們為什么需要smart pointer
眾所周知 新手寫的c++代碼是很恐怖 壓根就不能用 其中最大的原因就在于新手寫的代碼可能存在大量的內存泄漏 那么為什么新手無法很好的去掌握內存的東西呢 就是因為原生的c++并不像java那樣存在垃圾回收的機制 申請在堆區的資源都需要自己去回收 然而最痛苦的一件事情在于 指針的生命周期結束時 你會不小心就沒去回收他在堆區的資源 因為堆區資源的生命周期是很難把握的 有可能你析構了 直接導致野指針訪問異常那么為了解決這個問題 c++就推出了智能指針 其中最重要的三種指針就是shared_ptr unique_ptr weak_ptr 接下來讓我們來講講如何將智能指針的生命周期和堆區資源的生命周期綁定起來吧
其實也非常簡單 本質就是當這片堆區資源的引用計數變為0的時候就釋放這片內存
smart pointer基本概念之引用計數
先來說說引用計數 這個東西是stl保證了肯定是線程安全的 所以即使你在多個線程內同時去增加或者同時減少引用計數也并不會讓引用計數的值出現非你預期的結果
智能指針是和引用計數綁定在一起的 當你創建智能指針指向一片資源時 引用計數就加一 當智能指針析構時 引用計數就減一 當引用計數變為0時 堆區資源被析構
smart pointer之shared_ptr
讓我們來看看下一段代碼
int main()
{
std::shared_ptr<std::string> i(new std::string("its good"));
std::shared_ptr<std::string> j(new std::string("its bad"));
std::vector<std::shared_ptr<std::string>> smartPointer_vec;
for(int k=0;k<5;k++)
smartPointer_vec.emplace_back(i);
for (int k = 0; k < 4; k++)
smartPointer_vec.emplace_back(j);
for (auto &i : smartPointer_vec)
{
std::cout<<i->c_str();
std::cout << i.use_count() << " ";
std::cout << j.use_count() << std::endl;
i = nullptr;
}
std::cout << i->c_str();
std::cout << i.use_count() <<" ";
std::cout << j.use_count() << std::endl;
}
聰明人看輸出 你就能完全明白 當引用計數為0的時候就會析構 其他不多說了
重要講解:首先使用share_ptr去指向new出來的數據是性能低效的 最本質的原因在于 他會進行兩次內存分配 第一次是對象堆區資源的申請 然后才是引用計數堆區資源的申請 而使用make_shared可以只進行一次內存分配 所以他更快 并且更安全 并且c++標準委員會也推薦你這么做 關于make_shared等下講解
自定義deleter(也就是自定義刪除器)
先說我們為什么需要自定義刪除器 因為在某些情況下 我們希望當智能指針指向的堆區資源釋放的時候進行一些自定義操作也就是說你可以玩一些很花的操作 但是也是那句話 stl并不會執行任何安全檢查 崩了需要自己負責并且總所周知 new []這種形式的堆區資源需要我們使用delete[]來釋放 這就是最大的問題 shared_ptr默認是使用delete的 也就是說 當你使用shared_ptr去指向new []時如果不自定義刪除器 必然會造成內存泄漏 如下圖所示的一段代碼就是經典的內存泄漏
正確的寫法如下
即自定義一個刪除器 當然你也可以玩一些移動操作 也就是花哨的操作 當然花哨操作就很多了 我只演示其中一種如下圖所示
運行結果截圖如下:
Tips:當你非常清楚你在干什么的時候再玩 功力不夠 不要亂玩
shared_ptr之make_shared
上文我們說過 使用智能指針指向new出來的資源有一個問題就是他會進行兩次內存分配 而標準委員會推薦創建shared_ptr的方式是使用make_shared 讓我們來看看make_shared是如何進行堆區資源申請的 一個最簡單的例子如下
int main()
{
std::shared_ptr<int>p1(new int(5));
//下面這種方式比上面這種方式性能更快 并且更加安全
std::shared_ptr<int>p2 = make_shared<int>(5);
}
當你使用make_shared的時候 又想去使用智能指針指向一個數組的時候 一個推薦的做法如下
int main()
{
std::shared_ptr<std::vector<int>>p1(new std::vector<int>());
//下面這種方式比上面這種方式性能更快 并且更加安全
std::shared_ptr<std::vector<int>>p2 = make_shared<std::vector<int>>();
}
智能指針存在的問題之循環引用
那么現在我們來看看shared_ptr存在的一些問題 其中比較著名的一個問題就是循環引用 什么叫循環引用呢 本人的觀點是當你的智能指針指向的A堆區資源里又有智能指針去指向B堆區資源 而B堆區資源又存在一個智能指針來指向A堆區資源 而你能拿到的指針對半是全局或者是棧區的智能指針 你無法干預到堆區的智能指針的釋放 下面來看一個最簡單的例子造成的循環引用 代碼如下圖所示
class SmartPointerTest
{
public:
std::shared_ptr<SmartPointerTest> LoopRef{};
int p[1000]{};
};
int main()
{
std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest());
std::shared_ptr<SmartPointerTest>p2(new SmartPointerTest());
p1->LoopRef = p2;
p2->LoopRef = p1;
}
可以明顯看到 我們創建了兩個智能指針p1和p2 而p1指向的堆區資源里又有智能指針指向p2的堆區資源 同理p2 而當main函數結束的時候 p1 p2指針被釋放 但是 這個時候 因為兩片堆區資源的引用計數都沒被置為0 所以不會釋放 那么這片堆區內存也就永遠的泄漏了 這是所有循環引用的原型 無論任何再復雜的循環引用都是建立在這個最基本的循環引用之上的
解決循環引用之weak_ptr
我們現在希望有一個方法來解決循環引用的問題 并且我們也想去隨時拿到資源 那么我們該如何做呢 標準委員會也考慮到了這個問題 于是他提供了weak_ptr 當他指向一片堆區資源的時候 并不會讓這片堆區資源的引用計數加一 而是作為這片資源的觀察者 當需要這片資源的時候 隨時使用lock()函數來獲得一個shared_ptr來進行使用 下面讓我們來看看如何使用weak_ptr 基于上面的例子
class SmartPointerTest
{
public:
std::weak_ptr<SmartPointerTest> LoopRef{};
int p[1000]{};
};
int main()
{
std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest());
std::shared_ptr<SmartPointerTest>p2(new SmartPointerTest());
p1->LoopRef = p2;
p2->LoopRef = p1;
//當你想使用資源的時候 用下面的操作進行
std::cout << p1->LoopRef.lock()->p << std::endl;
}
輸出結果如下:
Tips:當然weak_ptr的作用遠遠不止如此 他存在的意義僅僅是你想共享資源但是你并不想增加引用計數 解決循環引用只是順便解決的優秀的程序員總是能知道在什么情況下使用何種指針來達到性能最優 lock()函數 顧名思義是要去給引用計數上鎖的 頻繁上鎖帶來的性能問題不用多說了吧
如果weak_ptr指向的資源已經被析構 那么他會拋出bad_weak_ptr的異常 請注意捕獲異常
智能指針問題
無法創建指向自己的智能指針(本質當創建自己的智能指針時會創建兩個所屬組)
什么叫無法創建指向自己的智能指針呢 看如下這段代碼
class SmartPointerTest
{
public:
std::weak_ptr<SmartPointerTest> LoopRef{};
int p[1000]{};
std::vector<std::shared_ptr<SmartPointerTest>> spt_vec;
void MemberFuncTest()
{
spt_vec.push_back(std::shared_ptr<SmartPointerTest>(this));
}
int operator[](int i)
{
return p[i];
}
};
int main()
{
std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest());
p1->MemberFuncTest();
std::cout<<p1.use_count()<<std::endl;
system("pause");
}
我們預期的結果是把指向自己的智能指針傳入 并且引用計數為2 但是運行結果如下:
并且程序會崩潰 為什么呢 因為你重復釋放了 這就是我說的 你會創建兩個組 而不是單純的增加引用計數 其本質還是濫用普通指針和智能指針引起的麻煩
解決方法如下
代碼如下 我們可以繼承于std::enable_shared_from_this來解決
class SmartPointerTest :std::enable_shared_from_this<SmartPointerTest>
{
public:
std::weak_ptr<SmartPointerTest> LoopRef{};
int p[1000]{};
std::vector<std::shared_ptr<SmartPointerTest>> spt_vec;
void MemberFuncTest()
{
spt_vec.push_back(std::shared_ptr<SmartPointerTest>(shared_from_this()));
}
int operator[](int i)
{
return p[i];
}
};
int main()
{
std::shared_ptr<SmartPointerTest>p1(new SmartPointerTest());
p1->MemberFuncTest();
std::cout<<p1.use_count()<<std::endl;
system("pause");
}
當你這樣繼承自enable_shared_from_this的時候你就可以將自身的智能指針傳入而不是創建一個新的組避免了重復釋放非常的方便
關于unique_ptr我們將會在下一篇文章進行詳細講解其實也很簡單就是他堆區資源的引用計數永遠只可能是一也就是說他的資源只可能被一個指針指向附帶而來的有一些小細節和普通的shared_ptr不同我們也就留在下一章再說了
原文鏈接:https://blog.csdn.net/qq_16401691/article/details/126425900
相關推薦
- 2022-04-15 ASP.NET?Core托管模型CreateDefaultBuilder()方法_基礎應用
- 2024-07-18 redisson分布式鎖中waittime的設置
- 2022-09-17 C++?中封裝的含義和簡單實現方式_C 語言
- 2022-05-03 Docker?Desktop啟動失敗的解決(Docker?failed?to?initialize?
- 2022-04-18 Taro 中的 用戶下拉事件,onPullDownRefresh
- 2022-04-23 elementui el-pagination 分頁組件封裝
- 2022-04-01 hive not in效率優化
- 2022-10-17 C++右值引用與move和forward函數的使用詳解_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支