C++中智能指針的原理、使用、實現

C++中智能指針的原理、使用、實現

轉自:https://www.cnblogs.com/wxquare/p/4759020.html

1 智能指針的作用

C++程序設計中使用堆內存是非常頻繁的操作,堆內存的申請和釋放都由程序員自己管理。程序員自己管理堆內存可以提高了程序的效率,但是整體來說堆內存的管理是麻煩的,C++11中引入了智能指針的概念,方便管理堆內存。使用普通指針,容易造成堆內存泄露(忘記釋放),二次釋放,程序發生異常時內存泄露等問題等,使用智能指針能更好的管理堆內存。

理解智能指針需要從下面三個層次:

  1. 從較淺的層面看,智能指針是利用了一種叫做RAII(資源獲取即初始化)的技術對普通的指針進行封裝,這使得智能指針實質是一個對象,行為表現的卻像一個指針。

  2. 智能指針的作用是防止忘記調用delete釋放內存和程序異常的進入catch塊忘記釋放內存。另外指針的釋放時機也是非常有考究的,多次釋放同一個指針會造成程序崩潰,這些都可以通過智能指針來解決。

  3. 智能指針還有一個作用是把值語義轉換成引用語義。C++和Java有一處最大的區別在于語義不同,在Java里面下列代碼:

    Animal a = new Animal();
    Animal b = a;
    

    你當然知道,這里其實只生成了一個對象,a和b僅僅是把持對象的引用而已。但在C++中不是這樣,

    Animal a;
    Animal b = a;
    

    這里卻是就是生成了兩個對象。關于值語言參考這篇文章http://www.cnblogs.com/Solstice/archive/2011/08/16/2141515.html。

2 智能指針的使用

智能指針在C++11版本之后提供,包含在頭文件<memory>中,shared_ptrunique_ptrweak_ptr

2.1 shared_ptr的使用

shared_ptr多個指針指向相同的對象。shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減為0時,自動刪除所指向的堆內存。shared_ptr內部的引用計數是線程安全的,但是對象的讀取需要加鎖。

  • 初始化。智能指針是個模板類,可以指定類型,傳入指針通過構造函數初始化。也可以使用make_shared函數初始化。不能將指針直接賦值給一個智能指針,一個是類,一個是指針。例如std::shared_ptr p4 = new int(1);的寫法是錯誤的
  • 拷貝和賦值。拷貝使得對象的引用計數增加1,賦值使得原對象引用計數減1,當計數為0時,自動釋放內存。后來指向的對象引用計數加1,指向后來的對象。
  • get函數獲取原始指針
  • 注意不要用一個原始指針初始化多個shared_ptr,否則會造成二次釋放同一內存
  • 注意避免循環引用,shared_ptr的一個最大的陷阱是循環引用,循環,循環引用會導致堆內存無法正確釋放,導致內存泄漏。循環引用在weak_ptr中介紹。
#include <iostream>
#include <memory>int main() {{int a = 10;std::shared_ptr<int> ptra = std::make_shared<int>(a);std::shared_ptr<int> ptra2(ptra); //copystd::cout << ptra.use_count() << std::endl;int b = 20;int *pb = &a;//std::shared_ptr<int> ptrb = pb;  //errorstd::shared_ptr<int> ptrb = std::make_shared<int>(b);ptra2 = ptrb; //assignpb = ptrb.get(); //獲取原始指針std::cout << ptra.use_count() << std::endl;std::cout << ptrb.use_count() << std::endl;}
}

2.2 unique_ptr的使用

unique_ptr “唯一” 擁有其所指對象,同一時刻只能有一個 unique_ptr 指向給定對象(通過禁止拷貝語義、只有移動語義來實現)。相比與原始指針unique_ptr用于其RAII的特性,使得在出現異常的情況下,動態資源能得到釋放。unique_ptr指針本身的生命周期:從unique_ptr指針創建時開始,直到離開作用域。離開作用域時,若其指向對象,則將其所指對象銷毀(默認使用delete操作符,用戶可指定其他操作)。unique_ptr指針與其所指對象的關系:在智能指針生命周期內,可以改變智能指針所指對象,如創建智能指針時通過構造函數指定、通過reset方法重新指定、通過release方法釋放所有權、通過移動語義轉移所有權。

#include <iostream>
#include <memory>int main() {{std::unique_ptr<int> uptr(new int(10));  //綁定動態對象//std::unique_ptr<int> uptr2 = uptr;  //不能賦值//std::unique_ptr<int> uptr2(uptr);  //不能拷貝std::unique_ptr<int> uptr2 = std::move(uptr); //轉換所有權uptr2.release(); //釋放所有權}//超過uptr的作用域,內存釋放
}

2.3 weak_ptr的使用

weak_ptr是為了配合shared_ptr而引入的一種智能指針,因為它不具有普通指針的行為,沒有重載operator*和->,它的最大作用在于協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。weak_ptr可以從一個shared_ptr或者另一個weak_ptr對象構造,獲得資源的觀測權。但weak_ptr沒有共享資源,它的構造不會引起指針引用計數的增加。使用weak_ptr的成員函數use_count()可以觀測資源的引用計數,另一個成員函數expired()的功能等價于use_count()==0,但更快,表示被觀測的資源(也就是shared_ptr的管理的資源)已經不復存在。weak_ptr可以使用一個非常重要的成員函數lock()從被觀測的shared_ptr獲得一個可用的shared_ptr對象, 從而操作資源。但當expired()==true的時候,lock()函數將返回一個存儲空指針的shared_ptr。

#include <iostream>
#include <memory>int main() {{std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);std::cout << sh_ptr.use_count() << std::endl;std::weak_ptr<int> wp(sh_ptr);std::cout << wp.use_count() << std::endl;if(!wp.expired()){std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr*sh_ptr = 100;std::cout << wp.use_count() << std::endl;}}//delete memory
}

2.4 循環引用

考慮一個簡單的對象建模——家長與子女:a Parent has a Child, a Child knowshis/her Parent。在Java 里邊很好寫,不用擔心內存泄漏,也不用擔心空懸指針,只要正確初始化myChild 和myParent,那么Java 程序員就不用擔心出現訪問錯誤。一個handle 是否有效,只需要判斷其是否non null。

public class Parent
{private Child myChild;
}
public class Child
{private Parent myParent;
}

在C++ 里邊就要為資源管理費一番腦筋。如果使用原始指針作為成員,Child和Parent由誰釋放?那么如何保證指針的有效性?如何防止出現空懸指針?這些問題是C++面向對象編程麻煩的問題,現在可以借助smart pointer把對象語義(pointer)轉變為值(value)語義,shared_ptr輕松解決生命周期的問題,不必擔心空懸指針。但是這個模型存在循環引用的問題,注意其中一個指針應該為weak_ptr。

  1. 原始指針的做法,容易出錯

    #include <iostream>
    #include <memory>class Child;
    class Parent;class Parent {
    private:Child* myChild;
    public:void setChild(Child* ch) {this->myChild = ch;}void doSomething() {if (this->myChild) {}}~Parent() {delete myChild;}
    };class Child {
    private:Parent* myParent;
    public:void setPartent(Parent* p) {this->myParent = p;}void doSomething() {if (this->myParent) {}}~Child() {delete myParent;}
    };int main() {{Parent* p = new Parent;Child* c =  new Child;p->setChild(c);c->setPartent(p);delete c;  //only delete one}return 0;
    }
    
  2. 循環引用內存泄露的問題

    #include <iostream>
    #include <memory>class Child;
    class Parent;class Parent {
    private:std::shared_ptr<Child> ChildPtr;
    public:void setChild(std::shared_ptr<Child> child) {this->ChildPtr = child;}void doSomething() {if (this->ChildPtr.use_count()) {}}~Parent() {}
    };class Child {
    private:std::shared_ptr<Parent> ParentPtr;
    public:void setPartent(std::shared_ptr<Parent> parent) {this->ParentPtr = parent;}void doSomething() {if (this->ParentPtr.use_count()) {}}~Child() {}
    };int main() {std::weak_ptr<Parent> wpp;std::weak_ptr<Child> wpc;{std::shared_ptr<Parent> p(new Parent);std::shared_ptr<Child> c(new Child);p->setChild(c);c->setPartent(p);wpp = p;wpc = c;std::cout << p.use_count() << std::endl; // 2std::cout << c.use_count() << std::endl; // 2}std::cout << wpp.use_count() << std::endl;  // 1std::cout << wpc.use_count() << std::endl;  // 1return 0;
    }
    
  3. 正確的做法

    #include <iostream>
    #include <memory>class Child;
    class Parent;class Parent {
    private://std::shared_ptr<Child> ChildPtr;std::weak_ptr<Child> ChildPtr;
    public:void setChild(std::shared_ptr<Child> child) {this->ChildPtr = child;}void doSomething() {//new shared_ptrif (this->ChildPtr.lock()) {}}~Parent() {}
    };class Child {
    private:std::shared_ptr<Parent> ParentPtr;
    public:void setPartent(std::shared_ptr<Parent> parent) {this->ParentPtr = parent;}void doSomething() {if (this->ParentPtr.use_count()) {}}~Child() {}
    };int main() {std::weak_ptr<Parent> wpp;std::weak_ptr<Child> wpc;{std::shared_ptr<Parent> p(new Parent);std::shared_ptr<Child> c(new Child);p->setChild(c);c->setPartent(p);wpp = p;wpc = c;std::cout << p.use_count() << std::endl; // 2std::cout << c.use_count() << std::endl; // 1}std::cout << wpp.use_count() << std::endl;  // 0std::cout << wpc.use_count() << std::endl;  // 0return 0;
    }
    

3 智能指針的設計與實現

下面是一個簡單智能指針的demo。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。每次創建類的新對象時,初始化指針并將引用計數置為1;當對象作為另一對象的副本而創建時,拷貝構造函數拷貝指針并增加與之相應的引用計數;對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數為減至0,則刪除對象),并增加右操作數所指對象的引用計數;調用析構函數時,構造函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。智能指針就是模擬指針動作的類。所有的智能指針都會重載 -> 和 * 操作符。智能指針還有許多其他功能,比較有用的是自動銷毀。這主要是利用棧對象的有限作用域以及臨時對象(有限作用域實現)析構函數釋放內存。

#include <iostream>
#include <memory>template<typename T>
class SmartPointer {
private:T* _ptr;size_t* _count;
public:SmartPointer(T* ptr = nullptr) :_ptr(ptr) {if (_ptr) {_count = new size_t(1);} else {_count = new size_t(0);}}SmartPointer(const SmartPointer& ptr) {if (this != &ptr) {this->_ptr = ptr._ptr;this->_count = ptr._count;(*this->_count)++;}}SmartPointer& operator=(const SmartPointer& ptr) {if (this->_ptr == ptr._ptr) {return *this;}if (this->_ptr) {(*this->_count)--;if (this->_count == 0) {delete this->_ptr;delete this->_count;}}this->_ptr = ptr._ptr;this->_count = ptr._count;(*this->_count)++;return *this;}T& operator*() {assert(this->_ptr == nullptr);return *(this->_ptr);}T* operator->() {assert(this->_ptr == nullptr);return this->_ptr;}~SmartPointer() {(*this->_count)--;if (*this->_count == 0) {delete this->_ptr;delete this->_count;}}size_t use_count(){return *this->_count;}
};int main() {{SmartPointer<int> sp(new int(10));SmartPointer<int> sp2(sp);SmartPointer<int> sp3(new int(20));sp2 = sp3;std::cout << sp.use_count() << std::endl;std::cout << sp3.use_count() << std::endl;}//delete operator
}

參考:

  1. 值語義:http://www.cnblogs.com/Solstice/archive/2011/08/16/2141515.html
  2. shared_ptr使用:http://www.cnblogs.com/jiayayao/archive/2016/12/03/6128877.html
  3. unique_ptr使用:http://blog.csdn.net/pi9nc/article/details/12227887
  4. weak_ptr的使用:http://blog.csdn.net/mmzsyx/article/details/8090849
  5. weak_ptr解決循環引用的問題:http://blog.csdn.net/shanno/article/details/7363480
  6. C++面試題(四)——智能指針的原理和實現

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532580.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532580.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532580.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Xctf練習sql注入--supersqli

三種方法 方法一 1 回顯正常 1’回顯不正常,報sql語法錯誤 1’ -- 回顯正常,說明有sql注入點,應該是字符型注入(# 不能用) 1’ order by 3 -- 回顯失敗,說明有2個注入點 1’ union select 1,2 -- 回顯顯示過濾語句: 1’; show databases -- 爆數據庫名 -1’; show tables …

深拷貝與淺拷貝、值語義與引用語義對象語義 ——以C++和Python為例

深拷貝與淺拷貝、值語義與引用語義/對象語義 ——以C和Python為例 值語義與引用語義&#xff08;對象語義&#xff09; 本小節參考自&#xff1a;https://www.cnblogs.com/Solstice/archive/2011/08/16/2141515.html 概念 在任何編程語言中&#xff0c;區分深淺拷貝的關鍵都…

一次打卡軟件的實戰滲透測試

直接打卡抓包, 發現有疑似企業網站,查ip直接顯示以下頁面 直接顯示了后臺安裝界面…就很有意思 探針和phpinfo存在 嘗試連接mysql失敗 fofa掃描為阿里云服務器 找到公司官網使用nmap掃描,存在端口使用onethink 查詢onethink OneThink是一個開源的內容管理框架&#xff0c;…

C++中類的拷貝控制

C中類的拷貝控制 轉自&#xff1a;https://www.cnblogs.com/ronny/p/3734110.html 1&#xff0c;什么是類的拷貝控制 當我們定義一個類的時候&#xff0c;為了讓我們定義的類類型像內置類型&#xff08;char,int,double等&#xff09;一樣好用&#xff0c;我們通常需要考下面…

centos7ubuntu搭建Vulhub靶場(推薦Ubuntu)

這里寫目錄標題一.前言總結二.成功操作&#xff1a;三.出現報錯&#xff1a;四.vulhub使用正文&#xff1a;一.前言總結二.成功操作&#xff1a;三.出現報錯&#xff1a;四.vulhub使用看完點贊關注不迷路!!!! 后續繼續更新優質安全內容!!!!!一.前言總結 二.成功操作&#xff1…

使用 PyTorch 數據讀取,JAX 框架來訓練一個簡單的神經網絡

使用 PyTorch 數據讀取&#xff0c;JAX 框架來訓練一個簡單的神經網絡 本文例程部分主要參考官方文檔。 JAX簡介 JAX 的前身是 Autograd &#xff0c;也就是說 JAX 是 Autograd 升級版本&#xff0c;JAX 可以對 Python 和 NumPy 程序進行自動微分。可以通過 Python的大量特征…

Yapi Mock 遠程代碼執行漏洞

跟風一波復現Yapi 漏洞描述&#xff1a; YApi接口管理平臺遠程代碼執行0day漏洞&#xff0c;攻擊者可通過平臺注冊用戶添加接口&#xff0c;設置mock腳本從而執行任意代碼。鑒于該漏洞目前處于0day漏洞利用狀態&#xff0c;強烈建議客戶盡快采取緩解措施以避免受此漏洞影響 …

C++ ACM模式輸入輸出

C ACM模式輸入輸出 以下我們都以求和作為題目要求&#xff0c;來看一下各種輸入輸出應該怎么寫。 1 只有一個或幾個輸入 輸入樣例&#xff1a; 3 5 7輸入輸出模板&#xff1a; int main() {int a, b, c;// 接收有限個輸入cin >> a >> b >> c;// 輸出結果…

CVE-2017-10271 WebLogic XMLDecoder反序列化漏洞

漏洞產生原因&#xff1a; CVE-2017-10271漏洞產生的原因大致是Weblogic的WLS Security組件對外提供webservice服務&#xff0c;其中使用了XMLDecoder來解析用戶傳入的XML數據&#xff0c;在解析的過程中出現反序列化漏洞&#xff0c;導致可執行任意命令。攻擊者發送精心構造的…

樹莓派攝像頭 C++ OpenCV YoloV3 實現實時目標檢測

樹莓派攝像頭 C OpenCV YoloV3 實現實時目標檢測 本文將實現樹莓派攝像頭 C OpenCV YoloV3 實現實時目標檢測&#xff0c;我們會先實現樹莓派對視頻文件的逐幀檢測來驗證算法流程&#xff0c;成功后&#xff0c;再接入攝像頭進行實時目標檢測。 先聲明一下筆者的主要軟硬件配…

【實戰】記錄一次服務器挖礦病毒處理

信息收集及kill&#xff1a; 查看監控顯示長期CPU利用率超高&#xff0c;懷疑中了病毒 top 命令查看進程資源占用&#xff1a; netstat -lntupa 命令查看有無ip進行發包 netstat -antp 然而并沒有找到對應的進程名 查看java進程和solr進程 ps aux &#xff1a;查看所有進程…

ag 搜索工具參數詳解

ag 搜索工具參數詳解 Ag 是類似ack&#xff0c; grep的工具&#xff0c;它來在文件中搜索相應關鍵字。 官方列出了幾點選擇它的理由&#xff1a; 它比ack還要快 &#xff08;和grep不在一個數量級上&#xff09;它會忽略.gitignore和.hgignore中的匹配文件如果有你想忽略的文…

CVE-2013-4547 文件名邏輯漏洞

搭建環境&#xff0c;訪問 8080 端口 漏洞說明&#xff1a; Nginx&#xff1a; Nginx是一款輕量級的Web 服務器/反向代理服務器及電子郵件&#xff08;IMAP/POP3&#xff09;代理服務器&#xff0c;在BSD-like 協議下發行。其特點是占有內存少&#xff0c;并發能力強&#xf…

CMake指令入門 ——以構建OpenCV項目為例

CMake指令入門 ——以構建OpenCV項目為例 轉自&#xff1a;https://blog.csdn.net/sandalphon4869/article/details/100589747 一、安裝 sudo apt-get install cmake安裝好后&#xff0c;輸入 cmake -version如果出現了cmake的版本顯示&#xff0c;那么說明安裝成功 二、c…

CVE-2017-7529Nginx越界讀取緩存漏洞POC

漏洞影響 低危&#xff0c;造成信息泄露&#xff0c;暴露真實ip等 實驗內容 漏洞原理 通過查看patch確定問題是由于對http header中range域處理不當造成&#xff0c;焦點在ngx_http_range_parse 函數中的循環&#xff1a; HTTP頭部range域的內容大約為Range: bytes4096-81…

Linux命令行性能監控工具大全

Linux命令行性能監控工具大全 作者&#xff1a;Arnold Lu 原文&#xff1a;https://www.cnblogs.com/arnoldlu/p/9462221.html 關鍵詞&#xff1a;top、perf、sar、ksar、mpstat、uptime、vmstat、pidstat、time、cpustat、munin、htop、glances、atop、nmon、pcp-gui、collect…

Weblogic12c T3 協議安全漏洞分析【CVE-2020-14645 CVE-2020-2883 CVE-2020-14645】

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 關注公眾號&#xff1a;b1gpig信息安全&#xff0c;文章推送不錯過 ## 前言 WebLogic是美國Oracle公司出品的一個application server,確切的說是一個基于JAV…

Getshell總結

按方式分類&#xff1a; 0x01注入getshell&#xff1a; 0x02 上傳 getwebshell 0x03 RCE getshell 0x04 包含getwebshell 0x05 漏洞組合拳getshell 0x06 系統層getcmdshell 0x07 釣魚 getcmdshell 0x08 cms后臺getshell 0x09 紅隊shell競爭分析 0x01注入getshell&#xff1a;…

編寫可靠bash腳本的一些技巧

編寫可靠bash腳本的一些技巧 原作者&#xff1a;騰訊技術工程 原文鏈接&#xff1a;https://zhuanlan.zhihu.com/p/123989641 寫過很多 bash 腳本的人都知道&#xff0c;bash 的坑不是一般的多。 其實 bash 本身并不是一個很嚴謹的語言&#xff0c;但是很多時候也不得不用。以下…

python 到 poc

0x01 特殊函數 0x02 模塊 0x03 小工具開發記錄 特殊函數 # -*- coding:utf-8 -*- #內容見POC.demo; POC.demo2 ;def add(x,y):axyprint(a)add(3,5) print(------------引入lambad版本&#xff1a;) add lambda x,y : xy print(add(3,5)) #lambda函數,在lambda函數后面直接…