C++入門到精通 ——第四章 智能指針

四、智能指針

Author: XFFer_

01 直接內存管理(new/delete)、創建新工程觀察內存泄漏

1 直接內存管理(new/delete)
2 創建新工程,觀察內存泄漏

直接內存管理(new/delete)

C語言中是malloc/free

定義初值
int *pointi = new int(100);
string *points = new string(5, 'a');	//"aaaaa"
vector<int> *pointv = new vector<int>{1, 2, 3, 4, 5};  

概念 “值初始化”:用()來初始化

int *pointi = new int();
auto pointi_t = new auto(pointi);
//前面的auto推斷出的是int **

推薦在delete後將該指針設置成nullptr
C++中出現了智能指針,會幫助程序猿收回內存。

創建新工程,觀察內存泄漏

MFC應用程序能夠在一定程度上(程序推出的時候),幫助發現內存泄漏。
MFC:微軟公司出的基礎的程序框架(MFC基礎類庫),可以生成一個帶窗口(帶界面)的程序框架,MFC簡化了很多界面開發工作。

快捷鍵 ctrl + ] 跳轉到對應的{}

02 new、delete探祕,智能指針概述、shared_ptr基礎

1 new/delete探祕

new/delete是什麼
operator new()和operator delete()
基本new如何記錄分配的內存大小供delete使用
申請和釋放一個數組
爲什麼new/delete、new[]/delete[]要配套使用

2 智能指針總述
3 shared_ptr基礎

常規初始化(shared_ptrnew配合)
make_shared函數

new/delete探祕

new/delete是什麼?

new/delete類對象時,有初始化/釋放的能力(調用構造函數/析構函數),這些是malloc/free所不能的。

operator new()和operator delete()

new/delete是操作符,operater new()和operator delete()是函數

  • new簡單理解爲做了兩件事:a)分配內存(實際上通過operator new());b)給內存初始化
  • delete也做了兩件事:a)反初始化(調用析構函數);b)釋放內存(operator delete())
基本new如何記錄分配的內存大小供delete使用

不同編譯器new內部有不同的實現方式,new內部有記錄機制,記錄分配出多少內存。

申請和釋放一個數組

int *p = new int[2];
delete[] p;
如果申請一個類數組,會在內存中存入該數組中類的個數,以便與調用等量的析構函數。

智能指針總述

  • 裸指針:直接用new返回的指針,強大靈活,但是需要開發者全程維護。
  • 智能指針:理解爲“裸指針”進行了包裝,能夠“自動釋放所指向的對象內存”。C++標準庫std提供了四種智能指針:
    • 都是類模版
      • auto_ptr(C++98)已經被unique_ptr取代
      • unique_ptr(C++11):獨佔式指針。同一時間內,只有一個指針指向該對象
      • shared_ptr(C++11):共享式指針。多個指針指向同一個對象,最後一個指針被銷燬時,這個對象會被銷燬
      • weak_ptr(C++11)用來輔助shared_ptr
  • 用來幫助我們進行動態分配對象(new出來的對象)的生命週期的管理。能夠有效防止內存泄漏

shared_ptr基礎

常規初始化(shared_ptr和new配合)

每個share_ptr的拷貝都指向相同的內存

工作原理引用計數。只有最後一個指向該內存的shared_ptr不需要再指向該對象時,纔會析構這個對象。計數的作用是每增加一個shared_ptrcount + 1,銷燬指針到count爲0時,就會釋放內存。

std::shared_ptr<int> pi(new int(100));	
//智能指針explicit不允許隱式轉換
//故std::shared_ptr<int> pi = new int;是錯誤的

make_share函數:標準庫裏的函數模版

在自定義刪除器時會受到限制

shared_ptr<int> pt = make_shared<int>(100);

03 shared_ptr常用操作、計數、自定義刪除器

1 shared_ptr引用計數的增加和減少

引用計數的增加
引用計數的減少

2 shared_ptr指針常用操作

use_count()
unique()
reset()
*解應用
get()
swap()
=nullptr
智能指針名字作爲判斷條件
指定刪除器以及數組問題

shared_ptr引用計數的增加和減少

每個shared_ptr都會記錄有多少個其他的shared_ptr指向相同的對象。

shared_ptr指針常用操作

  • use_count()返回多少個智能指針指向某個對象,主要用於調試目的
  • unique()是否該智能指針獨佔某個指向的對象,如果是,返回True
  • reset()復位/重置
    • 不帶參數時
      • 若該指針是唯一指向該對象的指針,那麼釋放該對象,並將該指針置空(nullptr)
      • 若該指針不是唯一指向該對象的指針,那麼不釋放該對象,但計數減一,該指針置空(nullptr)
    • 帶參數時(一般是new出來的指針)
      • 若該指針是唯一指向該對象的指針,那麼釋放該對象,並將該指針指向新對象
      • 若該指針不是唯一指向該對象的指針,那麼不釋放該對象,但計數減一,該指針指向新對象
  • *解應用獲得指針指向的對象
  • get()返回保存的指針(裸指針)。用於有些函數的參數需要的是一個內置裸指針而不是智能指針
  • swap()交換兩個智能指針所指向的對象
  • =nullptr
    • 將所指向的對象引用計數減一,若引用計數變爲0,則釋放該指針指向的對象
    • 將智能指針置空
  • 指定刪除器以及數組問題
    • 可以指定自己的刪除器函數取代系統的默認刪除器
    • 管理動態數組時
//定義一個刪除器函數
void deletefunc(int *p)
{
	delete []p;
}
shared_ptr<int> pt(new int[100], deletefunc);

//打包
template<typename T>
shared_ptr<T> make_array(size_t n)
{
	return shared_ptr<T>(new T[n], default_delete<T[]>());
}
shared_ptr pt = make_array<int>(3);	//長度爲3的整形數組,自定義刪除器

//lambda函數做自定義刪除器
shared_ptr<int> pt(new int[10], [](int *p) {
	delete []p;
	}

//default_delete做刪除器
shared_ptr<int> pt(new int[10], default_delete<int[]>());
	
//C++17
shared_ptr<int[]> pt(new int[10]);	//在<>中加[]

size_t 源碼中 -> typedef unsigned __int64 size_t;

  • 32位系統的size_t是4字節的unsigned int

  • 64位系統的size_t是8字節的unsigned long long

    unsigned int 0~4294967295
    int -2147483648~2147483647
    unsigned long 0~4294967295
    long -2147483648~2147483647
    long long的最大值:9223372036854775807
    long long的最小值:-9223372036854775808
    unsigned long long的最大值:18446744073709551615

    __int64的最大值:9223372036854775807
    __int64的最小值:-9223372036854775808
    unsigned __int64的最大值:18446744073709551615

weak_ptr概述、weak_ptr常用操作、尺寸

1 weak_ptr概述

weak_ptr的創建

2 weak_ptr常用操作

use_count()
expired()
reset()
lock()

3 尺寸問題

weak_ptr概述

weak_ptr用來輔助shared_ptr進行工作。這個智能指針指向一個由shared_ptr管理的對象。但是它並不控制所指向對象的生命週期(不會改變shared_ptr的引用計數)。shared_ptr能夠照常釋放所指向的對象。

weak_ptr的創建

一般都用shared_ptr初始化。

auto pi = make_shared<int>(100);
weak_ptr<int> pwi(pi);	
//不會改變強引用(strong ref)的引用計數,但是會增加弱引用(weak ref)計數

weak_ptr常用操作

  • use_count獲取當前所觀測資源的強引用計數
  • expired()是否過期,用來判斷所觀測資源是否被釋放
  • reset()將該弱引用指針設置爲
  • lock():功能是檢查weak_ptr所指向的對象是否存在。如果存在返回一個指向該對象的shared_ptr,如果不存在則返回一個空的shared_ptr
//接着
auto pi2 = pwi.lock();	//會使強引用加1,pwi仍是弱引用

尺寸問題

weak_ptr的尺寸和shared_ptr一樣大,是裸指針的2倍(8字節)。實際上在它們內部包含了兩個指針。

05 shared_ptr使用場景、陷阱、性能分析、使用建議

1 std::shared_ptr使用場景
2 std::shared_ptr使用陷阱分析
3 性能說明

尺寸問題
移動語義

std::shared_ptr使用陷阱分析

  • 慎用裸指針
    • 當把一個普通裸指針綁定到了一個shared_ptr上之後,內存管理的責任就交給了智能指針,這時就不應該再用裸指針訪問shared_ptr指向的內存了
    • 不能用裸指針初始化多個shared_ptr,會導致多個智能指針之間不關聯,刪除器會由於兩次釋放相同內存而報錯
  • 慎用get()返回的指針
    • get返回的指針不能delete,否則會異常
    • 不能將其他智能指針綁到get返回的指針上
  • 不要把類對象指針(this)作爲shared_ptr返回,改用enable_shared_from_this(工作中需要返回該調用函數本身的類的智能指針時使用)
    • 工作原理:enable_shared_from_this中又一個弱指針weak_ptr,這個弱指針能夠監視this,調用shared_from_this時,實際上調用了這個weak_ptr的lock方法
class CT : public enable_shared_from_this<CT>
{
public:
	shared_ptr<CT> getself()
	{
		return shared_from_this(); //這個是enable_shared_from_this類模板中的方法
	}
};

性能說明

尺寸問題

shared_ptrweak_ptr均佔8個字節,也就是兩個指針,一個指針指向該指向對象的智能指針;一個指針指向控制塊(包括強引用計數、弱引用計數、刪除器、分配器等)

移動語義

移動構造一個新的智能指針

shared_ptr<int> p1(new int(100));
shared_ptr<int> p2(std::move(p1));

會將p1指向的對象移動給p2,p1不再指向該對象(變成空指針)。

06 unique_ptr概述、常用操作

1 unique_ptr概述

常規初始化
make_unique函數

2 unique_ptr常用操作

unique_ptr概述

獨佔式的概念(專屬所有權):同一個時刻只能有一個unique_ptr指向一個對象。當被銷燬時,所指向的對象同時也被銷燬。

unique_ptr<int> pt(new int(103));

//c++14:make_unique
unique_ptr<int> pt = make_unique<int>(104);
auto pt = make_unique<int>(105);

unique_ptr常用操作

  • 移動語義
unique_ptr<string> ps1(new string("ingenious"));
unique_ptr<string> ps2(std::move(ps1));
  • release():放棄對指針的控制權,切斷指針和對象之間的聯繫。返回裸指針,並將該智能指針置空,該裸指針需手動釋放
unique_ptr<string> ps1(new string("ingenuous"));
unique_ptr<string> ps2(ps1.release());
  • reset()
    • 不帶參數:釋放智能指針指向的對象,並把指針置空
    • 帶參數:釋放智能指針指向的對象,並將智能指針指向括號內的新對象
unique_ptr<string> ps3(new string("fractious"));
ps3.reset(ps1.release());
  • = nullptr:釋放指向的對象並置空該智能指針
  • get():返回智能指針中保存的裸指針
  • swap():交換兩個指針所指向的對象
  • 轉換成shared_ptr形式:如果unique_ptr爲右值就可以轉換成shared_ptr形式
auto func()
{
	return unique_ptr<string> st(new string("fd"))//返回的是臨時對象,是右值
}

shared_ptr<string> p = func();

07 返回unique_ptr、刪除器、尺寸、智能指針總結

1 返回unique_ptr
2 指定刪除器
3 尺寸問題
4 智能指針總結

指定刪除器

格式: unique_ptr<指向的對象類型, 刪除器類型> 指針名

void mydelete(string* p)
{
	delete p;
	p = nullptr;
}

//typedef void (*pr) (string *);
//using pr = void (*) (string *);
typedef decltype(mydelete)* pr;
unique_ptr<string, pr> point(new string("hei"), mydelete); 

unique_ptr自定義刪除器需要在模板參數中加入刪除器函數的類型,例中提供了用函數指針做模板參數的情況。

尺寸問題

通常情況下,unique_ptr和裸指針的大小一樣(4字節)。但是自定義刪除器就可能增加所佔用的內存。

總結

  • 智能指針的設計目的:幫助釋放內存,以防內存泄漏
  • auto_ptr爲什麼被廢棄?
    • 不能再容器中保存
    • 不能從函數中返回auto_ptr
    • C++11提出的unique_ptr相比C++98的auto_ptr更安全
    • … …
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章