首頁 > 軟體

C++的智慧指標你真的瞭解嗎

2022-03-31 16:00:29

什麼是RAII

RAII(Resource Acquisition Is Initialization)是由C++之父提出的,中文翻譯為資源獲取即初始化,使用區域性物件來管理資源的技術稱為資源獲取即初始化;這裡的資源主要是指作業系統中有限的東西如記憶體(heap)、網路通訊端、互斥量、檔案控制程式碼等等,區域性物件是指儲存在棧的物件,它的生命週期是由作業系統來管理的,無需人工介入

RAII的原理

資源的使用一般經歷三個步驟:

  • 獲取資源(建立物件)
  • 使用資源
  • 銷燬資源(解構物件)

但是資源的銷燬往往是程式設計師經常忘記的一個環節,所以程式界就想如何在程式中讓資源自動銷燬呢?解決問題的方案就是:RAII,它充分的利用了C++語言區域性物件自動銷燬的特性來控制資源的生命週期

裸指標存在的問題

1.難以區分指向的是單個物件還是一個陣列

2.使用完指標之後無法判斷是否應該銷燬指標,因為無法判斷指標是否”擁有“指向的物件

3.在已經確定需要銷燬指標的情況下,也無法確定是用delete關鍵字刪除,還是有其他特殊的銷燬機制,例如通過將指標傳入某個特定的銷燬函數來摧毀指標

4.即使已經確定了銷燬指標的方法,由於1的原因,仍然無法確定到底是i用delete(銷燬單個物件)還是delete[](銷燬一個陣列)

5.假設上述的問題都解決了,也很難保證在程式碼的所有路徑中(分支結構,異常導致的挑戰),有且僅有一次銷燬指標的操作;任何一條路徑遺漏都可能導致記憶體的洩露,而銷燬多次則會導致未定義行為

6.理論上沒有方法來分辨一個指標是否處於懸掛狀態

auto_ptr

class Object
{
	int value;
public:
	Object(int x = 0) :value(x)
	{
		cout << "Create Object:" << this << endl;
	}
	~Object()
	{
		cout << "Destory Object:" << this << endl;
	}
	int& Value()
	{
		return value;
	}
};

template<class _Ty>
class my_auto_ptr
{
private:
	bool _Owns;
	_Ty* _Ptr;
public:
	my_auto_ptr(_Ty* p = NULL) :_Owns(p != NULL), _Ptr(p)
	{}
	~my_auto_ptr()
	{
		if (_Owns)
		{
			delete _Ptr;
		}
		_Owns = false;
		_Ptr = NULL;
	}
};

void fun()
{
	my_auto_ptr<Object> obj(new Object(10));
}

int main()
{
	fun();
}


在這裡將Object構建完成後,將其指標給到p,當函數結束去調動智慧指標的解構函式去釋放空間

若我們需要在fun()函數中,去呼叫Object類的方法obj->Value();

class Object
{
	int value;
public:
	Object(int x = 0) :value(x)
	{
		cout << "Create Object:" << this << endl;
	}
	~Object()
	{
		cout << "Destory Object:" << this << endl;
	}
	int& Value()
	{
		return value;
	}
};

template<class _Ty>
class my_auto_ptr
{
private:
	bool _Owns;
	_Ty* _Ptr;
public:
	my_auto_ptr(_Ty* p = NULL) :_Owns(p != NULL), _Ptr(p)
	{}
	~my_auto_ptr()
	{
		if (_Owns)
		{
			delete _Ptr;
		}
		_Owns = false;
		_Ptr = NULL;
	}
	_Ty* get()const
	{
		return _Ptr;
	}
	_Ty& operator*()const
	{
		return *(get());
	}
	_Ty* operator ->()const
	{
		return get();
	}
};

void fun()
{
	my_auto_ptr<Object> obj(new Object(10));
	cout << obj->Value() << endl;
	cout << (*obj).Value() << endl;
}

int main()
{
	fun();
}

通過運運算元過載,(*obj) 後將直接指向堆區(heap)的物件實體

若我們通過一個my_auto_ptr去建立另一個my_auto_ptr

class Object
{
	int value;
public:
	Object(int x = 0) :value(x)
	{
		cout << "Create Object:" << this << endl;
	}
	~Object()
	{
		cout << "Destory Object:" << this << endl;
	}
	int& Value()
	{
		return value;
	}
};

template<class _Ty>
class my_auto_ptr
{
private:
	bool _Owns;
	_Ty* _Ptr;
public:
	my_auto_ptr(_Ty* p = NULL) :_Owns(p != NULL), _Ptr(p)
	{}
	~my_auto_ptr()
	{
		if (_Owns)
		{
			delete _Ptr;
		}
		_Owns = false;
		_Ptr = NULL;
	}
	my_auto_ptr(const my_auto_ptr& obj):_Owns(obj._Owns),_Ptr(obj._ptr)
	{	
	}
	my_auto_ptr& operator=(const my_auto_ptr& _Y)
	{
		if(this == &_Y) return *this;
		if(_Owns)
		{
			delete _Ptr;
		}
		_Owns = _Y._Owns;
		_Ptr = _Y._Ptr;
		return 0;
	}

	_Ty* get()const
	{
		return _Ptr;
	}
	_Ty& operator*()const
	{
		return *(get());
	}
	_Ty* operator ->()const
	{
		return get();
	}
	void reset(_Ty* p = NULL)
	{
		if (_Owns)
		{
			delete _Ptr;
		}
		_Ptr = p;
	}
	_Ty* release()const
	{
		_Ty* tmp = NULL;
		if (_Owns)
		{
			((my_auto_ptr*)this)->_Owns = false; //常性進行修改
			tmp = _Ptr;
			((my_auto_ptr*)this)->_Ptr = NULL;
		}
		return tmp;
	}
};

void fun()
{
	my_auto_ptr<Object> pobja(new Object(10));
	my_auto_ptr<Object> pobjb(pobja);
}

int main()
{
	fun();
}

如果通過淺拷貝,則兩個指標擁有同一個資源,在解構的過程會造成資源的重複釋放導致崩潰

若設定為將其資源進行轉移

my_auto_ptr(const my_auto_ptr& obj):_Owns(obj._Owns),_Ptr(release())
{
}
my_auto_ptr& operator=(const my_auto_ptr& _Y)
{
	if(this == &_Y) return *this;
	if(_Owns)
	{
		delete _Ptr;
	}
	_Owns = _Y._Owns;
	_Ptr = _Y.release();
	return 0;
}
void fun(my_auto_ptr<Object> apx)
{
	int x = apx->Value();
	cout<<x<<endl;
}
int main()
{
	my_auto_ptr<Object> pobja(new Object(10));
	fun(pobja);
	int a = pobja->Value();
	cout<<a<<endl;
}

那麼上面的過程中,資源會進行轉移pobja將不再擁有資源,導致pobja失去資源進而程式崩潰

這也就是auto_ptr的侷限性,也導致該智慧指標的幾乎沒有使用

unique_ptr

該智慧指標屬於唯一性智慧指標,將拷貝構造刪除,也就不能將其新建另一個物件,同時也不能作為引數傳入

class Object
{
	int value;
public:
	Object(int x = 0) :value(x)
	{
		cout << "Create Object:" << this << endl;
	}
	~Object()
	{
		cout << "Destory Object:" << this << endl;
	}
	int& Value()
	{
		return value;
	}
};

int main()
{
	std::unique_ptr<Object> pobja(new Object(10));
	//std::unique_ptr<Object> pobjb(pobja); error
	//不允許
	std::unique_ptr<Object> pobjb(std::move(pobja));
}

通過移動賦值是可以的,通過明確的概念,對其資源進行轉移

同時unique_ptr可以區分其所指向的是一個單獨空間,或者是連續的空間

struct delete_ar_object
{
	void operator()(Object* op)
	{
		if(op == NULL) return;
		delete[] op;
	}
}
int main()
{
	std::unique_ptr<Object> pobja(new Object(10));
	std::unique_ptr<Object,delete_ar_object> pobjb(new Object[10]);
}

在這裡如果是連續空間,會呼叫刪除連續空間的刪除器;單獨空間則使用預設刪除器

unique_ptr在編寫的時候,有多個模板類,分別對應單個物件的方案和一組物件的方案

並且可以通過智慧指標指向fopen開啟的檔案物件,而檔案物件是同fclose去進行關閉的

struct delete_file
{
	void operator()(FILE *fp)
	{
		if(fp == NULL) return;
		fclose(fp);
	}
}
std::unique_ptr<FILE,delete_file> pfile(fopen("zyq.txt","w"));

這裡只需要將預設的刪除器,更改為對檔案物件的刪除器

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!


IT145.com E-mail:sddin#qq.com