亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C++ 智能指針的魅力你都了解嗎

 更新時間:2021年06月04日 09:42:19   作者:WhiteShirtI  
智能指針使用和普通指針類似。解引用一個智能指針返回它指向的對象。如果在一個條件判斷中使用智能指針,效果就是檢測它是否為空,本文給大家介紹C++ 智能指針的相關(guān)知識,感興趣的朋友一起看看吧

前情提要

我們知道除了靜態(tài)內(nèi)存和棧內(nèi)存外,每個程序還有一個內(nèi)存池,這部分內(nèi)存被稱為自由空間或者堆。程序用堆來存儲動態(tài)分配的對象即那些在程序運行時分配的對象,當(dāng)動態(tài)對象不再使用時,我們的代碼必須顯式的銷毀它們。

在C++中,動態(tài)內(nèi)存的管理是用一對運算符完成的:new和delete,ne:在動態(tài)內(nèi)存中為對象分配一塊空間并返回一個指向該對象的指針,delete指向一個動態(tài)獨享的指針,銷毀對象,并釋放與之關(guān)聯(lián)的內(nèi)存。

動態(tài)內(nèi)存管理經(jīng)常會出現(xiàn)兩種問題:一種是忘記釋放內(nèi)存,會造成內(nèi)存泄漏;一種是尚有指針引用內(nèi)存的情況下就釋放了它,就會產(chǎn)生引用非法內(nèi)存的指針。

為了更加容易(更加安全)的使用動態(tài)內(nèi)存,引入了智能指針的概念。智能指針的行為類似常規(guī)指針,重要的區(qū)別是它負(fù)責(zé)自動釋放所指向的對象

智能指針的原理

RAII:利用對象生命周期來控制程序資源。主要是通過對象的構(gòu)造函數(shù)來獲得資源的管理權(quán),然后再通過析構(gòu)函數(shù)來釋放所管理的資源。其原理就是把管理一份資源的責(zé)任托管給了一個對象

//RAII
template<class T>
class SmartPtr
{
public:
	//構(gòu)造函數(shù)獲取資源管理權(quán)
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	//通過析構(gòu)函數(shù)釋放資源
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};
class A
{
private:
	int _a = 10;
};

void test()
{
	//錯誤寫法
	int* ptr = new int[10];
	SmartPtr<int> sp(ptr);
	//立即初始化--申請資源時就立即綁定資源
	SmartPtr<int> sp2(new int);
	SmartPtr<A> sp3(new A);
}

這并不是一個智能指針對象,智能指針應(yīng)該要滿足以下條件

  • 實現(xiàn)RAII思想
  • 使用方式和指針一樣,例如需要支持*解引用和->操作

應(yīng)在類中添加以下操作的重載

T* operator->()
	{
		return _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}

智能指針和普通的指針的區(qū)別之一是智能指針不需要手動釋放空間

void test()
{
	//智能指針--編譯器調(diào)用析構(gòu)自動釋放資源--不存在內(nèi)存泄漏
	SmartPtr<A> sp(new A);
	(*sp)._a = 10;
	sp->_a = 100;

	//普通指針--手動釋放內(nèi)存
	int* p = new int;
	A* pa = new A;
	*p = 1;
	pa->_a = 10;
	//return  //提前結(jié)束普通指針就會導(dǎo)致內(nèi)存泄漏
	delete p;
	delete pa;
}

C++標(biāo)準(zhǔn)庫中的智能指針的使用

庫中的智能指針分為 auto_ptr、unique_ptr、 share_ptr
他們都需要引入頭文件#include <memory>才能使用

auto_ptr

auto_ptr是一種存在缺陷的智能指針(禁用

#include <memory>
using namespace std;
void test()
{
	auto_ptr<int> ap(new int);
	auto_ptr<int> ap2(new int(2));
	*ap = 10;
	*ap2 = 20;
}

auto_ptr指針進行賦值操作時會將資源進行轉(zhuǎn)移,目的是為了防止多個智能指針指向同一塊內(nèi)存資源。但是這種設(shè)計顯然不符合我們的需求

在這里插入圖片描述

我們來簡單模擬實現(xiàn)auto_ptr,看他底層是如何進行資源權(quán)的轉(zhuǎn)移的

//實現(xiàn)auto_ptr
template<class T>
class Auto_ptr
{
public:
	Auto_ptr(T* ptr)
		:_ptr(ptr)
	{}
	~Auto_ptr()
	{
		if (_ptr)
			delete _ptr;
	}

	T* operator->()
	{
		return _ptr;
	}
	T& operator*()
	{
		return *_ptr;
	}

	Auto_ptr(Auto_ptr<T>& ap)
		:_ptr(ap._ptr)
	{
		//資源管理權(quán)轉(zhuǎn)移 
		ap._ptr = nullptr;
	}

	Auto_ptr<T>& operator=(Auto_ptr<T>& ap)
	{
		if (this != &ap)
		{
			if (_ptr)
				delete _ptr;
			//資源管理權(quán)轉(zhuǎn)移 
			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}
		return *this;
	}
private:
	T* _ptr;
};

unique_ptr

unique_ptr智能指針是通過防拷貝來解決資源管理權(quán)限轉(zhuǎn)移問題----將unique_ptr賦值運算符函數(shù)和拷貝構(gòu)造函數(shù)設(shè)置為刪除函數(shù)

void test()
{
	unique_ptr<int> up(new int(10));
	unique_ptr<int> up2(up);//error
	unique_ptr<int> up3(new int(20));
	up = up3; //error
}

報錯原因:拷貝構(gòu)造和賦值重載函數(shù)都是已經(jīng)刪除的函數(shù)

在這里插入圖片描述

底層實現(xiàn):

template<class T>
class Unique_ptr
{
public:
	Unique_ptr(T* ptr)
		:_ptr(ptr)
	{}

	Unique_ptr(const Unique_ptr<T>& up) = delete;
	Unique_ptr<T>& operator=(const Unique_ptr<T>& up) = delete;

	~Unique_ptr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}
private:
	T* _ptr;
};

shared_ptr

shared_ptr是C++11中新提供的一種智能指針,不僅解決了資源管理權(quán)限轉(zhuǎn)移問題,還提供靠譜的拷貝功能

class A
{
public:
	int _a = 10;
	~A()
	{
		cout << "~A()" << endl;
	}
};

void test()
{
	shared_ptr<A> sp(new A);
	shared_ptr<A> sp2(new A);
	shared_ptr<A> sp3(sp2);//ok
	sp3 = sp;//ok
	sp->_a = 100;
	sp2->_a = 1000;
	sp3->_a = 10000;
	cout << sp->_a << endl;
	cout << sp2->_a << endl;
	cout << sp3->_a << endl;
}

運行結(jié)果:

在這里插入圖片描述

我們發(fā)現(xiàn)申請多少資源就會釋放多少資源,此時的sp和sp3共享一份資源,修改sp3也就相等于修改了sp。所以最終都會打印10000。那共享了一份資源,是如何實現(xiàn)資源只釋放一次呢?----引用計數(shù)

我們可以通過shared_ptr提供的接口use_count()來查看,當(dāng)前有多少個智能指針來管理同一份資源

void test()
{
	shared_ptr<A> sp(new A);
	cout << sp.use_count() << endl;//1
	shared_ptr<A> sp2(sp);
	cout << sp.use_count() << endl;//2
	cout << sp2.use_count() << endl;//2
	shared_ptr<A> sp3(new A);
	cout << sp.use_count() << endl;//2
	cout << sp2.use_count() << endl;//2
	cout << sp3.use_count() << endl;//1
	sp3 = sp;
	sp3 = sp2;
	cout << sp.use_count() << endl;//2
	cout << sp2.use_count() << endl;//2
	cout << sp3.use_count() << endl;//2
}

運行截圖:之所以中間會有調(diào)析構(gòu)函數(shù),是因為當(dāng)sp3指向sp時,sp3的引用計數(shù)為0,則會調(diào)用析構(gòu)函數(shù)來釋放資源。此時sp創(chuàng)建的資源就有3個指智能指針來管理

在這里插入圖片描述

圖解

在這里插入圖片描述

在實現(xiàn)時,我們應(yīng)該確保一個資源只對應(yīng)一個計數(shù)器,而不是每個智能指針都有各自的計數(shù)器。所以我們可以將資源和計數(shù)器綁定在一起,此時指向同一份資源的智能指針,訪問的也都是同一個計數(shù)器
成員變量:成員變量應(yīng)該有兩個變量,分別是資源指針的變量_ptr和計數(shù)器變量_countPtr,他們都是一個指針類型的變量
拷貝構(gòu)造函數(shù):在拷貝構(gòu)造函數(shù)中,應(yīng)該將當(dāng)前對象的指針指向要拷貝的對象的資源,而且還要拷貝它的計數(shù)器,最后還需要將計數(shù)器進行++
賦值運算符重載:我們不能判斷兩個對象是否相等,而應(yīng)該為只要兩個對象的資源不同,那么才需要進行賦值。在賦值中,先讓當(dāng)前對象的計數(shù)器進行–,如果為0則表示當(dāng)前對象的資源是只被當(dāng)前對象管理,則需要釋放資源。然后再將當(dāng)前對象指針修改為要拷貝的對象的資源,并且拷貝它的計數(shù)器。最后還要對計數(shù)器進行++操作
析構(gòu)函數(shù):判斷當(dāng)前對象的資源的計數(shù)器,先進行–操作,在判斷計數(shù)器是否為0,如果為0才會將資源釋放,不為0則什么都不做

template<class T>
class Shared_ptr
{
public:
	Shared_ptr(T* ptr)
		:_ptr(ptr)
		, _countPtr(new size_t(1))//初始化為1
	{}
	Shared_ptr(const Shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _countPtr(sp._countPtr)
	{
		//計數(shù)器累加
		++(*_countPtr);
	}
	Shared_ptr<T> operator=(const Shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			//本身計數(shù)器自減
			//計數(shù)器為0,則當(dāng)前對象需要釋放資源
			if (--(*_countPtr) == 0)
			{
				delete _ptr;
				delete _countPtr;
			}

			_ptr = sp._ptr;
			_countPtr = sp._countPtr;

			++(*_countPtr);
		}
		return *this;
	}

	~Shared_ptr()
	{
		//計數(shù)器自減
		if (--(*_countPtr) == 0)
		{
			delete _ptr;
			delete _countPtr;
			_ptr = nullptr;
			_countPtr = nullptr;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	size_t* _countPtr;//計數(shù)器指針
};

我們實現(xiàn)的shared_ptr智能指針在多線程的場景下其實是不存在線程安全問題的----引用計數(shù)器指針是一個共享變量,多個線程進行修改時會導(dǎo)致計數(shù)器混亂。導(dǎo)致資源提前被釋放或者會產(chǎn)生內(nèi)存泄漏問題
我們來看看一下代碼, 如果是安全的,那么最后析構(gòu)函數(shù)應(yīng)該只被調(diào)用一次

void fun(const Shared_ptr<A>& sp, int n)
{
	for (int i = 0; i < n; ++i)
		Shared_ptr<A> copy(sp);//創(chuàng)建copy智能指針
}

void test()
{
	Shared_ptr<A> sp(new A);
	int n = 100000;
	thread t1(fun, ref(sp), n);
	thread t2(fun, ref(sp), n);
	t1.join();
	t2.join();
}

運行結(jié)果1:我們發(fā)現(xiàn)并沒有調(diào)用對象的析構(gòu)函數(shù),說明此時產(chǎn)生了內(nèi)存泄漏的問題

在這里插入圖片描述

運行結(jié)果2:調(diào)用兩次析構(gòu)函數(shù),也就說明一份資源被釋放兩次。

在這里插入圖片描述

我們可以在類中提供獲取計數(shù)器的值的接口

size_t getCount()
	{
		return *_countPtr;
	}

然后再代碼中運行并獲取計數(shù)器的值,發(fā)現(xiàn)計數(shù)器的值并沒有為0,所以就不會調(diào)用調(diào)用析構(gòu)函數(shù)

在這里插入圖片描述

所以我們可以在修改計數(shù)器的地方進行加鎖保護。而這個鎖不能為全局變量的鎖,資源之間不能被有影響,否則當(dāng)一個資源進行加鎖修改時,另一個資源會被收到影響,此時會影響代碼的執(zhí)行效率。應(yīng)當(dāng)給每一個計數(shù)器提供單獨的鎖
這里++操作和–操作都進行封裝

template<class T>
class Shared_ptr
{
public:
	Shared_ptr(T* ptr)
		:_ptr(ptr)
		, _countPtr(new size_t(1))//初始化為1
		, _mtx(new mutex)
	{}
	Shared_ptr(const Shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _countPtr(sp._countPtr)
		,_mtx(sp._mtx)
	{
		//計數(shù)器累加
		//++(*_countPtr);
		addCount();
	}
	Shared_ptr<T> operator=(const Shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			//本身計數(shù)器自減
			//計數(shù)器為0,則當(dāng)前對象需要釋放資源
			//if (--(*_countPtr) == 0)
			if (subCount() == 0)
			{
				delete _ptr;
				delete _countPtr;
				delete _mtx;
			}

			_ptr = sp._ptr;
			_countPtr = sp._countPtr;

			addCount();
		}
		return *this;
	}

	~Shared_ptr()
	{
		//計數(shù)器自減
		if (subCount() == 0)
		{
			delete _ptr;
			delete _countPtr;
			delete _mtx;
			_ptr = nullptr;
			_countPtr = nullptr;
			_mtx = nullptr;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}

	size_t getCount()
	{
		return *_countPtr;
	}

	size_t addCount()
	{
		_mtx->lock();
		++(*_countPtr);
		_mtx->unlock();
		return *_countPtr;
	}

	size_t subCount()
	{
		_mtx->lock();
		--(*_countPtr);
		_mtx->unlock();
		return *_countPtr;
	}
private:
	T* _ptr;
	size_t* _countPtr;//計數(shù)器指針
	mutex* _mtx;
};

運行結(jié)果:我們發(fā)現(xiàn)多線程場景下,也是都可以正常釋放的

在這里插入圖片描述

循環(huán)引用問題

shared_ptr其實也存在一些小問題,也就是循環(huán)引用問題
我們先來看看以下代碼

struct ListNode
{
	shared_ptr<ListNode> _next;
	shared_ptr<ListNode> _prev;
	int _data;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

void test()
{
	shared_ptr<ListNode> n1(new ListNode);
	shared_ptr<ListNode> n2(new ListNode);

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}

運行結(jié)果:我們發(fā)現(xiàn)并沒有釋放資源,計數(shù)器也在自增

在這里插入圖片描述

圖解:

在這里插入圖片描述
在這里插入圖片描述

在C++11中,專門為了解決這個問題,又引入了一種新的智能指針waek_ptr,這種指針稱為弱指針。在賦值或者拷貝的時候,計數(shù)器并不會進行++。析構(gòu)時也并不會進行真正資源的釋放。waek_ptr不能單獨使用,其最大的作用就是解決shared_ptr循環(huán)引用的問題。

struct ListNode
{
	weak_ptr<ListNode> _next;
	weak_ptr<ListNode> _prev;
	int _data;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

void test()
{
	shared_ptr<ListNode> n1(new ListNode);
	shared_ptr<ListNode> n2(new ListNode);

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
}

運行結(jié)果:

在這里插入圖片描述

在我們自己實現(xiàn)的shared_ptr中,我們在釋放資源時都單單以delete來釋放,而在我們申請空間方式中并非就只用new來申請空間們也有可能是用malloc來申請,則此時就應(yīng)該要用free來釋放。所以我們還要給智能指針添加一個刪除器

void test()
{
	Shared_ptr<A> sp(new A[100]);//調(diào)用析構(gòu)會報錯
}

刪除器主要可以通過仿函數(shù)來實現(xiàn)

template<class T>
struct DeleteDel
{
	void operator()(T* ptr)
	{
		delete ptr;
	}
};

template<class T>
struct FreeDel
{
	void operator()(T* ptr)
	{
		free(ptr);
	}
};

template<class T>
struct DeleteArrDel
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};
template<class T, class Del = DeleteDel<T>>
class Shared_ptr
{
public:
	Shared_ptr(T* ptr)
		:_ptr(ptr)
		, _countPtr(new size_t(1))//初始化為1
		, _mtx(new mutex)
	{}
	Shared_ptr(const Shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _countPtr(sp._countPtr)
		,_mtx(sp._mtx)
	{
		//計數(shù)器累加
		//++(*_countPtr);
		addCount();
	}
	Shared_ptr<T> operator=(const Shared_ptr<T>& sp)
	{
		if (_ptr != sp._ptr)
		{
			//本身計數(shù)器自減
			//計數(shù)器為0,則當(dāng)前對象需要釋放資源
			//if (--(*_countPtr) == 0)
			if (subCount() == 0)
			{
				//delete _ptr;
				//通過刪除器來釋放空間
				_del(_ptr);
				delete _countPtr;
				delete _mtx;
			}

			_ptr = sp._ptr;
			_countPtr = sp._countPtr;

			addCount();
		}
		return *this;
	}

	~Shared_ptr()
	{
		//計數(shù)器自減
		if (subCount() == 0)
		{
			//delete _ptr;
			//通過刪除器來釋放空間
			_del(_ptr);
			delete _countPtr;
			delete _mtx;
			_ptr = nullptr;
			_countPtr = nullptr;
			_mtx = nullptr;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}

	size_t getCount()
	{
		return *_countPtr;
	}

	size_t addCount()
	{
		_mtx->lock();
		++(*_countPtr);
		_mtx->unlock();
		return *_countPtr;
	}

	size_t subCount()
	{
		_mtx->lock();
		--(*_countPtr);
		_mtx->unlock();
		return *_countPtr;
	}
private:
	T* _ptr;
	size_t* _countPtr;//計數(shù)器指針
	mutex* _mtx;
	Del _del;
};

在這里插入圖片描述

以上就是C++ 一篇文章讓你知道智能指針的魅力的詳細(xì)內(nèi)容,更多關(guān)于C++智能指針的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語言實現(xiàn)簡單的掃雷游戲

    C語言實現(xiàn)簡單的掃雷游戲

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)簡單的掃雷游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • C語言實現(xiàn)memcpy函數(shù)的使用示例

    C語言實現(xiàn)memcpy函數(shù)的使用示例

    在C語言中,我們可以自己實現(xiàn) memcpy 函數(shù)來實現(xiàn)內(nèi)存數(shù)據(jù)的拷貝操作,本文就來介紹一下C語言實現(xiàn)memcpy函數(shù)的使用示例,感興趣的可以了解一下
    2023-09-09
  • C語言冷門知識之你可能沒聽過的柔性數(shù)組

    C語言冷門知識之你可能沒聽過的柔性數(shù)組

    柔性數(shù)組(Flexible Array)是引入的一個新特性,它允許你在定義結(jié)構(gòu)體時創(chuàng)建一個空數(shù)組,而這個數(shù)組的大小可以在程序運行的過程中根據(jù)你的需求進行更改特別注意的一點是:這個空數(shù)組必須聲明為結(jié)構(gòu)體的最后一個成員,并且還要求這樣的結(jié)構(gòu)體至少包含一個其他類型的成員
    2021-10-10
  • C語言學(xué)習(xí)之關(guān)鍵字的示例詳解

    C語言學(xué)習(xí)之關(guān)鍵字的示例詳解

    關(guān)鍵字,這名字一聽,就很關(guān)鍵。而有些關(guān)鍵字,你可能不是很了解,更別談使用。所以,這篇文章將帶你見識常見的關(guān)鍵字,一起領(lǐng)略它們的風(fēng)采吧
    2022-10-10
  • 淺析c++函數(shù)參數(shù)和返回值

    淺析c++函數(shù)參數(shù)和返回值

    c++一直以來是一個關(guān)注效率的代碼,這樣關(guān)于函數(shù)的參數(shù)傳遞和返回值的接收,是重中之重,這篇文章主要介紹了c++函數(shù)參數(shù)和返回值,需要的朋友可以參考下
    2023-05-05
  • vs運行時報C4996代碼錯誤的問題解決

    vs運行時報C4996代碼錯誤的問題解決

    C4996錯誤的意思:是VS覺得strcpy這函數(shù)不安全,建議你使更安全的函數(shù),那么如何解決呢,本文主要介紹了vs運行時報C4996代碼錯誤的問題解決,感興趣的可以了解一下
    2024-01-01
  • 淺談C++中對象的復(fù)制與對象之間的相互賦值

    淺談C++中對象的復(fù)制與對象之間的相互賦值

    這篇文章主要介紹了淺談C++中對象的復(fù)制與對象之間的相互賦值,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下
    2015-09-09
  • 淺析C++編程當(dāng)中的線程

    淺析C++編程當(dāng)中的線程

    這篇文章主要介紹了淺析C++編程當(dāng)中的線程,線程在每一種編程語言中都是重中之重,需要的朋友可以參考下
    2015-07-07
  • 在Ubuntu中安裝VSCode并配置C/C++開發(fā)環(huán)境的方法步驟

    在Ubuntu中安裝VSCode并配置C/C++開發(fā)環(huán)境的方法步驟

    這篇文章主要介紹了在Ubuntu中安裝VSCode并配置C/C++開發(fā)環(huán)境的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • C語言每日練習(xí)之求兩個矩陣的乘積詳解

    C語言每日練習(xí)之求兩個矩陣的乘積詳解

    這篇文章主要介紹了如何求兩個矩陣的乘積,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-11-11

最新評論