最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一篇文章帶你了解C++智能指針詳解

 更新時間:2021年08月13日 11:39:14   作者:z向前  
這篇文章主要介紹了c++ 智能指針基礎的相關資料,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下,希望能給你帶來幫助

為什么要有智能指針?

因為普通的指針存在以下幾個問題:

  • 資源泄露
  • 野指針
    •  未初始化
    • 多個指針指向同一塊內存,某個指針將內存釋放,別的指針不知道
  • 異常安全問題
  • 如果在 malloc和free 或者 new和delete 之間如果存在拋異常,那么也會導致內存泄漏。

資源泄漏示例代碼:

int main(){
	int *p = new int;
	*p = 1;
	p = new int; // 未釋放之前申請的資源,導致內存泄漏 
	delete p;
	
	return 0;
}

野指針示例代碼:

int main(){
	int *p1 = new int;
	int *p2 = p1;
	delete p1; 
	*p2 = 1; // 申請的內存已經被釋放掉了,  
	
	return 0;
}
int main(){
	int *p;
	*p = 1; // 程序直接報錯, 使用了未初始化的變量
	return 0;
}

解決方法:智能指針

智能指針的使用及原理

  • 具有RALL 特性
  • 重載了 operator* 和 operator ->,使其具有了指針一樣的行為

RALL

RALL(Resource Acquistion Is Initialization)是一種利用對象生命周期來控制程序資源(如內存,文件句柄,網絡連接,互斥量等)的簡單技術。

在對象構造時獲取資源,接著控制對資源的訪問使之在對象的生命周期內始終保持有效,最后在對象析構的時候釋放資源。相當于利用 對象 管理了一份資源。這樣的優(yōu)勢在于

1.不需要顯式的釋放資源(對象析構時,自動釋放資源)

2.采用這種方式,對象所需的資源在其生命周期內始終保持有效。

智能指針就是一個實例出來的對象

C++98版本的庫中就提供了auto_ptr的智能指針。但是 auto_ptr存在當對象拷貝或者賦值之后,前面的對象就懸空了。

C++11 提供更靠譜的并且支持拷貝的 shared_ptr

shared_ptr :

通過引用計數的方式實現多個shared_ptr 對象之間共享資源。

shared_ptr在其內部,給每個資源都維護了著一份計數,用來記錄該份資源被幾個對象共享。在對象被銷毀時(也就是析構函數調用),就說明自己不使用該資源了,對象的引用計數減一。如果引用計數是0,就說明自己是最后一個使用該資源的對象,必須釋放該資源;如果不是0,就說明除了自己還有其他對象在使用該份資源,不能釋放該資源,否則其他對象就成野指針了

unique_ptr :

確保一個對象同一時刻只能被一個智能指針引用,可以轉移所有權(可以從一個智能指針轉移到另一個智能指針)

auto_ptr :

C++11 已棄用, 與unique_ptr 類似

使用時,需包含頭文件

 #include <memory>

shared_ptr的使用注意事項

創(chuàng)建

1. 
shared_ptr<int> ptr{new int(3)};
2.
shared_ptr<int> ptr;
ptr.reset(new int(3));
3.
shared_ptr<int> ptr = make_shared<int>(3);

shared_ptr 支持使用比較運算符,使用時,會調用共享指針內部封裝的原始指針的比較運算符。

支持

==、!=、<、<=、>、>=

使用 比較運算符 的前提 必須是 同類型

示例:

shared_ptr<int> p1 = make_shared<int>(1);
shared_ptr<int> p2 = make_shared<int>(2);
shared_ptr<int> p3;
shared_ptr<double> p4 = make_shared<double>(1);

bool b1 = p1 < p2; 		// true
bool b2 = p1 > p3;		// true, 非NULL 指針與 NULL 指針相比 ,都是大于
bool b3 = p3 == p3;		// true
bool b4 = p4 < p2		// 編譯失敗,類型不一致

shared_ptr 可以使用強制類型轉換,但是不能使用普通的強制類型轉換符

1.shared_ptr 強制類型轉換符 允許將其中包含的指針強制轉換為其它類型

2.不能使用普通的強制類型轉換運算符,否則會導致未定義行為

3.shared_ptr 的強制類型轉換運算符包括
static_pointer_cast
dynamic_pointer_cast
const_pointer_cast

示例:

shared_ptr<void> p(new int);	// 內部保留 void* 指針
static_pointer_cast<int*>(p);	// 正確的 強制類型轉換方式
shared_ptr<int> p1(static_cast<int*>(p.get()));	// 錯誤的強制類型轉換方式,未定義錯誤

多個 shared_ptr 不能擁有同一個對象

利用代碼理解

示例:

class Mytest{
public:
	Mytest(const string& str)
	:_str(str){}
	~Mytest(){
		std::cout << _str << "destory" << std::endl;
	}
private:
	string _str;
};

int main(){
	Mytest* p = new Mytest("shared_test");
	shared_ptr<Mytest> p1(p); 	// 該對象可以正常析構
	shared_ptr<Mytest> p2(p); // 對象銷毀時,錯誤,讀取位置 0xDDDDDDDD 時發(fā)生訪問沖突。
	return 0;
}

上述代碼, 共享指針 p1 對象在程序 結束時,調用析構,釋放了p 所指向的空間, 當 p2 進行析構的時候,又釋放p所指向的空間, 但是由于已經釋放過了, 重復釋放已經釋放過的內存,導致段錯誤。

可以使用 shared_from_this 避免這種問題

改進代碼:

class Mytest:public enable_shared_from_this<Mytest> {
public:
    Mytest(const string& str)
        :_str(str) {}
    ~Mytest() {
        std::cout << _str << "destory" << std::endl;
    }
    shared_ptr<Mytest> GetSharedptr() {
        return shared_from_this();
    }
private:
    string _str;
};
int main() {
    Mytest* p = new Mytest("shared_test");
    shared_ptr<Mytest> p1(p);
    shared_ptr<Mytest> p2 = p->GetSharedptr(); // 正確做法
    
    return 0;
}

shared_ptr 的銷毀

shared_ptr 在初始化的時候,可以定義刪除器,刪除器可以定義為 普通函數、匿名函數、函數指針等符合要求的可調用對象

示例代碼:

void delFun(string* p) {
    std::cout << "Fun delete " << *p << endl;
    delete p;
}
int main() {

    std::cout << "begin" << std::endl;
    shared_ptr<string> p1;
    {
        shared_ptr<string> p2(new string("p1"), [](string* p) {
            std::cout << "Lamda delete " << *p << std::endl;
            delete p;
        });
        p1 = p2;
        shared_ptr<string> p3(new string("p3"), delFun);
    }
    std::cout << "end" << std::endl;
    return 0;
}

執(zhí)行結果:

begin
Fun delete p3
end
Lamda deletep1

分析結果:

首先 ,p3在{ }作用域內 ,生命周期最先結束,調用delFun作為刪除器

其次,p2 也在{ } 作用域內,生命周期也結束了,但是因為 p1 和 p2 指向了同一個對象,所以p2 銷毀只是將其 對象 引用計數 -1。

最后,程序運行結束,p1銷毀,其對象引用計數-1 變?yōu)?,調用 刪除器,銷毀對象。

shared_ptr<char> p(new char[10]); // 編譯能夠通過,但是會造成資源泄漏
// 正確做法
shared_ptr<char> p(new char[10], [](char* p){
	delete p[];
	});
// 正確做法
shared_ptr<char> p(new char[10], default_delete<char[]>());
  • 可以為數組創(chuàng)建一個shared_ptr ,但是這樣會造成資源泄露。因為 shared_ptr 提供默認的刪除調用的是 delete,而不是 delete[]
  • 可以使用自定義刪除器,刪除器中使用 delete[]
  • 可以使用 default_delete 作為刪除器,因為它使用 delete[]

shared_ptr 存在的問題:

1.循環(huán)引用
不同對象相互引用,形成環(huán)路

2.想要共享但是不想擁有對象

shared_ptr 的線程安全問題

1. shared_ptr 對象中引用計數是多個shared_ptr對象共享的,兩個線程中shared_ptr的引用計數同時++或–,這個操作不是原子的,引用計數原來是1,++了兩次,可能還是2 這樣引用計數就錯亂了。會導致資源未釋放或者程序崩潰的問題。所以只能指針中引用計數++、–是需要加鎖的,也就是說引用計數的操作是線程安全的。

2.shared_ptr 管理的對象存放在堆上,兩個線程中同時去訪問,會導致線程安全問題。

// 1.因為線程安全問題是偶現性問題,main函數的n改大一些概率就變大了,就
容易出現了。
void SharePtrFunc(shared_ptr<Date>& sp, size_t n)
{
	cout << sp.Get() << endl;
	for (size_t i = 0; i < n; ++i)
	{
		// 這里智能指針拷貝會++計數,智能指針析構會--計數,這里是線程安全的。
		shared_ptr<Date> copy(sp);
		// 這里智能指針訪問管理的資源,不是線程安全的。所以我們看看這些值兩個線程++了2n次,但是最終看到的結果,并一定是加了2n
		copy->_year++;
		copy->_month++;
		copy->_day++;
	}
}
int main()
{
	shared_ptr<Date> p(new Date);
	cout << p.Get() << endl;
	const size_t n = 100;
	thread t1(SharePtrFunc, p, n);
	thread t2(SharePtrFunc, p, n);
	t1.join();
	t2.join();
	cout << p->_year << endl;
	cout << p->_month << endl;
	cout << p->_day << endl;
	return 0;
}

shared_ptr 的循環(huán)引用

struct ListNode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
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;
}

循環(huán)引用代碼分析:

node1和node2兩個智能指針對象指向兩個節(jié)點,引用計數變成1,不需要手動delete。

node1的_next指向node2,node2的_prev指向node1,引用計數變成2。

node1和node2析構,引用計數減到1,但是_next還指向下一個節(jié)點。但是_prev還指向上一個節(jié)點。

也就是說_next析構了,node2就釋放了。

也就是說_prev析構了,node1就釋放了。

但是_next屬于node的成員,node1釋放了,_next才會析構,而node1由_prev管理,_prev屬于node2成員,所以這就叫循環(huán)引用,誰也不會釋放。

在這里插入圖片描述

解決方案:在引用計數的場景下,把節(jié)點中的_prev和_next改成weak_ptr就可以了

原理:

node1->_next = node2;和node2->_prev = node1;時weak_ptr的_next和_prev不會增加

node1和node2的引用計數。

struct ListNode
{
	int _data;
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;
	~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	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;
}

unique_ptr

  • 同一個對象,只能有唯一的一個 unique_ptr 指向它
  • 繼承了自動指針 auto_ptr,
  • 有助于避免發(fā)生異常時導致的資源泄漏

unique_ptr的使用

unique_ptr 定義了*、-> 運算符,沒有定義 ++ 之類的指針算法

unique_ptr 不允許使用賦值語法進行初始化,必須使用普通指針直接初始化

unique_ptr 可以為 空

unique_ptr 不能使用普通的復制語義賦值, 可以使用 C++11 的 move() 函數

unique_ptr 獲得新對象時,會銷毀之前的對象

unique_ptr 防止拷貝的原理:

// C++98防拷貝的方式:只聲明不實現+聲明成私有
UniquePtr(UniquePtr<T> const &);
UniquePtr & operator=(UniquePtr<T> const &);
// C++11防拷貝的方式:delete
UniquePtr(UniquePtr<T> const &) = delete;
UniquePtr & operator=(UniquePtr<T> const &) = delete;

總結

本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關注腳本之家的更多內容!

相關文章

  • C++11中初始化列表initializer lists的使用方法

    C++11中初始化列表initializer lists的使用方法

    C++11引入了初始化列表來初始化變量和對象,自定義類型,如果想用初始化列表就要包含initializer_list頭文件
    2021-09-09
  • C語言判斷回文數的小例子

    C語言判斷回文數的小例子

    這篇文章主要介紹了C語言判斷回文數的小例子,有需要的朋友可以參考一下
    2014-01-01
  • VisualStudio2022 cmake配置opencv開發(fā)環(huán)境

    VisualStudio2022 cmake配置opencv開發(fā)環(huán)境

    本文主要介紹了VisualStudio2022 cmake配置opencv開發(fā)環(huán)境,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-08-08
  • C++中函數重載實例詳解

    C++中函數重載實例詳解

    這篇文章主要介紹了C++中函數重載實例詳解的相關資料,需要的朋友可以參考下
    2017-03-03
  • QT6實現音頻可視化的示例代碼

    QT6實現音頻可視化的示例代碼

    這篇文章主要為大家詳細介紹了一個基于QT6的音頻可視化Demo,文中的示例代碼,具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-12-12
  • c語言快速排序算法示例代碼分享

    c語言快速排序算法示例代碼分享

    快速排序使用分治法(Divide and conquer)策略來把一個串行(list)分為兩個子串行(sub-lists)
    2014-02-02
  • C語言實現linux網卡檢測精簡版

    C語言實現linux網卡檢測精簡版

    這篇文章主要為大家詳細介紹了C語言實現linux網卡檢測的精簡版,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • C++中的函數匯總

    C++中的函數匯總

    這篇文章主要介紹了 C++中的函數匯總的相關資料,需要的朋友可以參考下
    2017-08-08
  • 用C語言實現猜數字游戲

    用C語言實現猜數字游戲

    這篇文章主要為大家詳細介紹了用C語言實現猜數字游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-10-10
  • 詳解C++如何高效利用CPU緩存

    詳解C++如何高效利用CPU緩存

    高效利用CPU緩存是編寫高性能C++代碼的關鍵之一,所以這篇文章小編主要來和大家介紹一下C++如何實現高效利用CPU緩存,需要的可以參考一下
    2024-02-02

最新評論

郓城县| 东明县| 宜兰县| 额济纳旗| 台州市| 涞水县| 天门市| 嘉黎县| 汾西县| 定南县| 诸暨市| 丹凤县| 汉中市| 永泰县| 新源县| 乌拉特前旗| 民乐县| 贞丰县| 措美县| 宣恩县| 罗平县| 新和县| 凤凰县| 开原市| 香格里拉县| 恭城| 邹城市| 邻水| 子长县| 西华县| 柏乡县| 上杭县| 丹凤县| 山东省| 凤庆县| 华蓥市| 电白县| 宜宾市| 台北市| 西城区| 沿河|