1. 程式人生 > 程式設計 >C++如何用智慧指標管理記憶體資源

C++如何用智慧指標管理記憶體資源

1.簡介

C++作為一門應用廣泛的高階程式語言,卻沒有像Java、C#等語言擁有垃圾回收(Garbage Collection )機制來自動進行記憶體管理,這也是C++一直被詬病的一點。C++在發展的過程中,一直致力於解決記憶體洩漏,C++雖然基於效率的考慮,沒有采用垃圾回收機制,但從C++98開始,推出了智慧指標(Smart Pointer)來管理記憶體資源,以彌補C++在記憶體管理上的技術空白。

智慧指標是C++程式設計師們一件管理記憶體的利器,使用智慧指標管理記憶體資源,實際上就是將申請的記憶體資源交由智慧指標來管理,是RAII技術的一種實現。RAII是C++的之父Bjarne Stroustrup教授提出的概念,RAII全稱是“Resource Acquisition is Initialization”,直譯過來是“資源獲取即初始化”,也就是說在建構函式中獲取資源,在解構函式中釋放資源。因為C++的語言機制保證了,當一個物件建立的時候,自動呼叫建構函式,當物件超出作用域的時候會自動呼叫解構函式。所以,在RAII的指導下,我們應該使用類來管理資源,將資源和物件的生命週期繫結。

“資源獲取即初始化”,在使用智慧指標管理記憶體資源時,“資源”指的是通過new或malloc申請的記憶體資源,“初始化”指的是使用申請的記憶體資源來初始化棧上的智慧指標類物件。使用智慧指標管理記憶體資源的好處顯而易見,通過智慧指標物件在宣告週期結束時,自動呼叫解構函式,在解構函式中完成對記憶體資源的釋放,即自動的呼叫記憶體資源的釋放程式碼,避免因忘記對記憶體資源的釋放導致記憶體洩漏。

2.例項

下面看一個使用由C++11引入的智慧指標unique_ptr來管理記憶體資源的例子。

#include <memory>
#include <iostream>
using namespace std;

class A
{
public:
	A() {}
	~A() 
	{
		cout<<"A's destructor"<<endl;
	}
	void Hello() 
	{
		cout<<"use smart pointer to manage memory resources as far as possible"<<endl;
	}
};

int main()
{
	unique_ptr<A> pA(new A);
	pA->Hello();
	return 0;
}

程式輸出:

use smart pointer to manage memory resources as far as possible
A's destructor

可見在main()函式結束後,類A的解構函式被自動呼叫,完成了記憶體資源了釋放。

在建立智慧指標物件時,也可以暫時不指定記憶體資源,先建立一個空的智慧指標物件。空智慧指標物件不可以進行任何操作,但可以使用 get() 成員函式來判斷是否存在記憶體資源,如果為空則可以指定記憶體資源。類似於如下操作:

unique_ptr<int> pInt;
if (pInt.get()==nullptr)
{
	pInt.reset(new int(8));
	cout<<*pInt<<endl;
}

使用 unique_ptr 智慧指標來管理記憶體資源時,是對記憶體資源的獨佔式管理,即記憶體資源的所有權不能進行共享,同一時刻只能有一個 unique_ptr 物件佔有某個記憶體資源。如果發生賦值或拷貝構造,則會在編譯期報錯,因為unique_ptr禁止了拷貝語義,提高了程式碼的安全性。

unique_ptr<int> pInt(new int(8));
unique_ptr<int> pInt1=pInt;	//編譯報錯
unique_ptr<int> pInt2(pInt);	//編譯報錯

當然,可以通過移動語義完成記憶體資源的所有權轉移,轉移之後,原智慧指標物件變為空智慧指標物件,不能再對記憶體資源進行任何操作,否則會發生執行時錯誤,但我們也可以使用get()成員函式進行判空處理。

unique_ptr<int> pInt(new int(8));
unique_ptr<int> pInt1=std::move(pInt);		//轉移所有權
*pInt=6;																//對空智慧指標進行賦值操作將報執行時錯誤
if(!pInt.get())														//判空處理更安全
{
	*pInt=6;
}

獨佔式的記憶體資源管理可以使用 unique_ptr 來完成,但是如果想對記憶體資源進行共享式管理,那麼 unique_ptr 就無能為力了。shared_prt 使用引用計數來實現對記憶體資源的共享式管理,當對記憶體資源的引用計數變為0時,由最後一個對記憶體資源擁有管理權的智慧指標物件完成對記憶體資源的釋放。

#include <memory>
#include <iostream>
using namespace std;

class A
{
public:
	A() {}
	~A()
	{
		cout << "A's destructor" << endl;
	}
	void Hello()
	{
		cout << "use smart pointer to manage memory resources as far as possible" << endl;
	}
};

int main()
{
	shared_ptr<A> spInt(new A);		//接管記憶體資源
	cout << "reference count "<<spInt.use_count() << endl;
	shared_ptr<A> spInt1 = spInt;	//spInt1獲取記憶體資源的管理權
	spInt1->Hello();
	cout << "reference count " << spInt.use_count() << endl;
	spInt1.reset();						//spInt1放棄對記憶體資源的管理權
	cout << "reference count " << spInt.use_count() << endl;
}

程式編譯執行結果:

reference count 1
use smart pointer to manage memory resources as far as possible
reference count 2
reference count 1
A's destructor

3.智慧指標使用注意事項

智慧指標雖然增強了安全性,避免了潛在的記憶體洩漏,但是我們在使用時還是應該遵守一定的規則,以保證程式碼的健壯性。
(1)smart_ptr<T> 不等於 T*,使用時不能完全按照T*來使用。因為smart_ptr<T>本質上是類物件,一個用於管理記憶體資源的智慧指標類物件,而T*是一個指向型別T的指標,二者不能隨意地轉換和賦值;

(2)使用獨立的語句將newed物件置入智慧指標,因為使用臨時智慧指標物件可能會引發記憶體洩漏。比如下面的語句:

process(shared_ptr<A>(new A),foo());

實際上對 process() 函式呼叫時編譯器需要完成如下三步為process()準備好實參。

(1)呼叫函式foo();
(2)執行new A表示式;
(3)呼叫shared_ptr<A>建構函式,初始化智慧指標物件。

實際上,不同的編譯器在執行上述三個語句時可能會有不同的順序,如果編譯器將(2.2)放在(2.1)之前執行,執行順序如下:

(1)執行new A表示式;
(2)呼叫函式foo();
(3)呼叫shared_ptr<A>建構函式,初始化智慧指標物件。

如果在呼叫函式foo()時丟擲異常,那麼new A表示式產生的指向堆物件指標將會丟失,於是產生了記憶體洩漏。解決辦法就是使用獨立的語句將newed物件置入智慧指標,做法如下:

shared_ptr<A> spA(new A);
process(spA,foo());

以上就是C++如何用智慧指標管理記憶體資源的詳細內容,更多關於c++ 智慧指標管理記憶體的資料請關注我們其它相關文章!