網站首頁 編程語言 正文
一、C++11智能指針概述
在C++中,動態內存的使用時有一定的風險的,因為它沒有垃圾回收機制,很容易導致忘記釋放內存的問題,具體體現在異常的處理上。想要釋放掉拋異常的程序的一些內存,往往需要多次拋異常,這種處理方式是十分麻煩的。
智能指針的本質就是使用一個對象來接管一段開辟的空間,在該對象在銷毀的時候,自動調用析構函數來釋放這段內存。
因此智能指針的本質是一個類,類中最主要的對象是一個指針,該類的析構函數就是銷毀該指針指向的空間,使用智能指針的本質就是將一個指向動態開辟空間的指針賦給該類中的指針。不過這樣的處理過程會有一定的問題,比如淺拷貝等。
C++標準庫提供了兩種智能指針類型來管理動態對象,由于該對象的行為酷似指針,所以稱為智能指針。它們分別是shared_ptr以及unique_ptr。還提供了一個weak_ptr它主要是為了解決shared_ptr的循環引用問題。
shared_ptr允許多個指針指向同一個對象,unique_ptr則獨占所指向的對象。
二、C++98中的智能指針
在很早以前,大佬們就已經認識到了內存釋放的問題,因此為標準庫中增加了一個類:auto_str。它有著和unique_str智能指針類似的功能,它雖然成功的將一個開辟的資源塞給了一個類,不過存在很嚴重的問題,一些公司已經明令禁止使用它了:
auto_ptr<int> sptr1(new int);
auto_ptr<int> sptr2(sptr1);
*sptr1;
此時如果對sptr1進行解引用操作,會發生報錯。要了解報錯的原因,我們需要了解它的大致底層原理,作為第一個出現的智能指針,它只是簡單執行了將資源轉移,以及在析構中加入資源釋放,還有一些解引用的運算符重載函數:
template<class T>
class MyAuto
{
private:
T* _ptr;
public:
MyAuto(T* ptr)
:_ptr(ptr)
{}
~MyAuto()
{
if (_ptr != nullptr)
{
cout << "delete: " << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
}
MyAuto(MyAuto<T>& Ptr)
{
_ptr = Ptr._ptr;
Ptr._ptr = nullptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
};
可以發現,最終是淺拷貝的鍋。因為在進行資源轉移的時候,必須將原來的指針置為nullptr,否則析構的時候會析構兩次。而將其置為nullptr之后再要使用該指針對其進行解引用就會發生崩潰。
三、C++11中的智能指針
1.unique_ptr
unique_ptr處理上述問題簡單而粗暴,即不讓進行拷貝操作:
unique_ptr<int> sptr1(new int);
unique_ptr<int> sptr2(sptr1);
直接進行報錯處理。
我們也可以猜測出它的實現方式,那就是在拷貝構造和賦值構造的后面加上delete關鍵字。
template<class T>
class MyUnique
{
private:
T* _ptr;
public:
MyUnique(T* ptr)
:_ptr(ptr)
{}
~MyUnique()
{
if (_ptr != nullptr)
{
cout << "delete: " << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
}
MyUnique(MyUnique<T>& Ptr) = delete;
MyUnique& operator=(MyUnique<T>& Ptr) = delete;
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
};
2.shared_ptr
(1)引用計數器
shared_ptr是使用最多的智能指針,即它可以進行拷貝構造。
- 每一個智能指針類都有一個專門用于記錄該智能指針指向的資源的指針個數的計數器。
- 當多了一個智能指針指向該資源,則對所有指向該資源的智能指針的計數器進行++操作,當一個智能指針不再指向該資源的時候·,所有指向該資源的智能指針的計數器進行–操作。
- 當某一個智能指針將其–到0的時候由該智能指針釋放該資源。從而解決了不讓拷貝的根本問題:防止資源釋放多次。
- 同時智能指針有一個use_count函數來返回計數器的值。
shared_ptr<int> sptr1(new int(1));
shared_ptr<int> sptr2(sptr1);
shared_ptr<int> sptr3(sptr2);
cout << sptr1.use_count() << endl;
cout << sptr2.use_count() << endl;
cout << sptr2.use_count() << endl;
cout << "資源釋放成功" << endl;
(2)線程安全
涉及到共享,我們不得不將線程安全問題考慮進來,很顯然shared_ptr無論是要管理的資源的使用,還是要指向的該資源對應的計數器的加減操作,都不是線程安全的。
- 對于要管理的資源來說,如果多個線程不去使用該資源,是不會產生問題的。因此如果需要使用該資源由于代碼量的不同位置,C++為了保證性能,希望用戶來自己保證它的線程安全,即由用戶自己來加鎖解鎖。
- 而對于資源計數器來說,只要增加一個智能指針就會++,減少一個就會–,其邏輯明確簡單,因此shared_ptr為其加了鎖。
template<class T>
class MyShared
{
private:
T* _ptr;
mutex* _pmtx;
int* _pcount;
public:
MyShared(T* ptr)
:_ptr(ptr),
_pmtx(new mutex),
_pcount(new int(1))
{}
void AddCount()
{
_pmtx->lock();
(*_pcount)++;
_pmtx->unlock();
}
void DelCount()
{
_pmtx->lock();
bool flag = false;
if (--(*_pcount) == 0)
{
if (_ptr != nullptr)
{
cout << "delete: " << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
delete _pcount;//當為0的時候刪除計數器
_pcount = nullptr;
flag = true;
}
_pmtx->unlock();
if (flag == true)
{
delete _pmtx;
_pmtx = nullptr;
}
}
MyShared(MyShared<T>& sp)
:_ptr(sp._ptr),
_pcount(sp._pcount),
_pmtx(sp._pmtx)
{
AddCount();
}
MyShared& operator=(MyShared<T>& sp)
{
if (_ptr != sp._ptr)
{
DelCount();//釋放管理的舊資源
_ptr = sp._ptr;
_pcount = sp._pcount;
_pmtx = sp._pmtx;
AddCount();//對管理的新資源的計數器進行++
}
return *this;
}
//獲取引用計數
int use_count()
{
return *_pcount;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
};
(3)刪除器
如果不是new出來的對象如何通過智能指針進行管理呢?其實shared_ptr設計了一個刪除器來解決這一問題。
template<class T>
struct FreeFunc
{
void operator()(T* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
template<class T>
struct DeleteArrayFunc
{
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
???????};
此時使用malloc進行初始化的時候就也可以進行清理空間了:
FreeFunc<int> freeFunc;
shared_ptr<int> sp1((int*)malloc(4), freeFunc);
DeleteArrayFunc<int> deleteArrayFunc;
shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
3.weak_ptr
(1)shared_ptr中的循環調用問題
循環調用問題在一些特殊的情況下會產生:
1.node1和node2兩個智能指針指向兩個節點,引用計數變成1,我們不需要手動delete。
2.node1的_next指向node2,node2的_prev指向node1,引用計數變成2。
3.node1和node2析構,引用計數減到1,但是_next還指向下一個節點。但是_prev還指向上一個節點。
4.也就是說_next析構了,node2就釋放了。
5.也就是說_prev析構了,node1就釋放了。
6.但是_next屬于node的成員,node1釋放了,_next才會析構,而node1由_prev管理,_prev屬于node2成員,所
以這就叫循環引用,誰也不會釋放。
struct ListNode
{
shared_ptr<ListNode> _next;
shared_ptr<ListNode> _prev;
};
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1 ->_next = node2;
node2 -> _prev = node1;
通俗來講,就是此時如果想釋放node2,那么就需要delete(n1->next),但是如果要釋放n1->next就必須delete(n1),而要deleten1又需要delete(node2->prev)因此如果不讓prev指向n就沒有問題。
(2)weak_ptr
struct ListNode
{
std::weak_ptr<ListNode> _next;
std::weak_ptr<ListNode> _prev;
int _val;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
std::shared_ptr<ListNode> node1(new ListNode);
std::shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
//...
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
return 0;
}
原文鏈接:https://blog.csdn.net/qq_51492202/article/details/127111768
相關推薦
- 2022-11-08 go語言Pflag?Viper?Cobra?核心功能使用介紹_Golang
- 2022-06-19 python?rsa和Crypto.PublicKey.RSA?模塊詳解_python
- 2022-09-04 python?matplotlib庫繪圖實戰之繪制散點圖_python
- 2022-10-04 Go語言底層原理互斥鎖的實現原理_Golang
- 2022-11-05 Swift?Access?Control訪問控制與斷言詳細介紹_Swift
- 2023-01-15 圖鄰接矩陣可視化解析_python
- 2023-05-09 Linux?解壓縮文件到指定目錄_linux shell
- 2022-08-19 python如何使用contextvars模塊源碼分析_python
- 最近更新
-
- 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同步修改后的遠程分支