1. 程式人生 > 其它 >c++11總結12——四個智慧指標:shared_ptr、uinque_ptr、weak_ptr和auto_ptr

c++11總結12——四個智慧指標:shared_ptr、uinque_ptr、weak_ptr和auto_ptr

技術標籤:c++11智慧指標

前言

智慧指標的作用是管理一個指標。申請的空間在函式結束時忘記釋放,就會造成記憶體洩漏。使用智慧指標可以很大程度上避免這個問題,因為智慧指標是一個類,當超出類的作用域時,類會自動呼叫解構函式來釋放資源。所以智慧指標的原理是在函式結束時自動釋放記憶體空間,而不需要手動釋放記憶體空間。

1. unique_ptr

1.1 獨享管理物件所有權

unique_ptr獨享被管理物件指標的所有權

unique_ptr物件包裝一個原始指標,並負責其生命週期。當該物件被銷燬時,會在其解構函式中刪除關聯的原始指標。

一個錯誤的例子(不允許直接複製):

std::unique_ptr<string> p1(new string("c++"));
std::unique_ptr<string> p2;
p2 = p1;  //error

一個正確的例子:

class A {
public:
	A(int id) :mId(id) {
		cout << "A::A" << endl;
	}

	~A() {
		cout << "~A::A" << endl;
	}

	int getId() const
	{
		return mId;
	}
private:
	int mId;
};

int main()
{
	{
		std::unique_ptr<A> pA(new A(1));
		cout << pA->getId() << endl;
	}

	system("pause");
    return 0;
}

列印如下:

std::unique<A>的物件pA接收原始指標作為引數。當出作用域(正常退出或異常退出)時,在pA的解構函式中,呼叫類A(原始指標)的解構函式,刪除關聯的原始指標,這樣就不用手動刪除原始指標了。

1.2 建立物件——make_unique

auto pA = std::make_unique<A>(1);

1.3 獲取被管理物件的原始指標

A* a1 = pA.get();

1.4 重置unique_ptr物件

呼叫reset()函式,將釋放相關聯的原始指標並使unique_ptr物件為空

std::unique_ptr<A> pA = std::make_unique<A>(1);
//A* a1 = pA.get();
pA.reset();

if (nullptr == pA)
{
	cout << "pA is NULL" << endl;
}

1.5 轉移unique_ptr的控制權

std::unique_ptr<A> pA = std::make_unique<A>(1);
std::unique_ptr<A> pB = std::move(pA);

if (nullptr == pA)
	cout << "pA is NULL" << endl;

if(nullptr != pB)
	cout << "pB is not NULL" << endl;

cout << "pB->getId() is " << pB->getId() << endl;

利用std::move將pA的控制權轉移給pB,轉移後pA的指標為空。

1.6 釋放關聯的原始指標

在 unique_ptr 物件上呼叫release()將釋放其關聯的原始指標的所有權,並返回原始指標。這裡是釋放所有權,並沒有delete原始指標,reset()會delete原始指標。

std::unique_ptr<A> pA = std::make_unique<A>(1);
A* a1 = pA.release();

if (nullptr == pA)
{
	cout << "pA is NULL" << endl;
}

2. shared_ptr

shared_ptr是一個標準的共享所有權的智慧指標,允許多個指標指向同一個物件。利用引用計數的方式實現了對所管理的物件的所有權的分享,即允許多個shared_ptr共同管理同一個物件。

每個shared_ptr物件在內部指向兩個記憶體位置:

1)指向物件的指標;

2)用於控制引用計數資料的指標;

共享所有權如何在參考計數的幫助下工作:
1)當新的 shared_ptr 物件與指標關聯時,則在其建構函式中,將與此指標關聯的引用計數增加1。

2)當任何 shared_ptr 物件超出作用域時,則在其解構函式中,它將關聯指標的引用計數減1。如果引用計數變為0,則表示沒有其他 shared_ptr 物件與此記憶體關聯,在這種情況下,它使用delete函式刪除該記憶體。

2.1 使用原始指標建立shared_ptr物件

方法1:

std::shared_ptr<int> p1(new int());
cout << "p1.use_count() = " << p1.use_count() << endl;  //1

方法2:

auto p1 = std::make_shared<int>();
cout << "p1.use_count() = " << p1.use_count() << endl;  //1

std::make_shared 一次性為int物件和用於引用計數的資料都分配了記憶體,而new操作符只是為int分配了記憶體

2.2 分離原始指標

auto p1 = std::make_shared<int>();
cout << "p1.use_count() = " << p1.use_count() << endl;  //1

p1.reset();
cout << "p1.use_count() = " << p1.use_count() << endl;  //0

執行reset後引用計數會減一,如果引用計數為0,則刪除指標。

auto p1 = std::make_shared<int>();
cout << "p1.use_count() = " << p1.use_count() << endl;  //1

p1.reset(new int(1));
cout << "p1.use_count() = " << p1.use_count() << endl;  //1

p1 = nullptr;
cout << "p1.use_count() = " << p1.use_count() << endl;  //0

這種情況下,它將在內部指向新指標,因此其引用計數將再次變為1。p1置為nullptr,引用計數變為0。

2.3 自定義刪除器Deleter

1)回撥函式

shared_ptr的解構函式中刪除內部原始指標,預設呼叫的delete()函式。當我們需要析構delete[]時,需要自己設計回撥函式刪除。

class A
{
public:
	A() {
		cout << "A" << endl;;
	}
	~A() {
		cout << "~A" << endl;;
	}
};

void deleter(A *p)
{
	cout << "DELETER FUNCTION CALLED" << endl;
	delete[] p;
}

int main(){
   
   std::shared_ptr<A> pA(new A[3], deleter);

}

列印結果:

2)使用函式物件作為刪除器

class Deleter
{
public:
	void operator() (A *p) {
		std::cout << "DELETER FUNCTION CALLED\n";
		delete[] p;
	}
};

int main()
{
    std::shared_ptr<A> p3(new A[3], Deleter());
}

3)使用lamba作為刪除器(該方式較簡潔)

std::shared_ptr<A> p4(new A[3], [](A * x) {
	std::cout << "DELETER FUNCTION CALLED\n";
	delete[] x;
});

2.4 NULL檢測

不分配值的情況下預設為nullptr。

std::shared_ptr<int> p1;

if (nullptr == p1)
{
	cout << "p1 is NULL" << endl;
}

2.5 常見問題

2.5.1不要使用同一個原始指標構造 shared_ptr

int *num = new int(10);
std::shared_ptr<int> p1(num);

std::shared_ptr<int> p2(p1);  // 正確使用方法
std::shared_ptr<int> p3(num); // 不推薦

std::cout << "p1 Reference = " << p1.use_count() << std::endl; // 輸出 2
std::cout << "p2 Reference = " << p2.use_count() << std::endl; // 輸出 2
std::cout << "p3 Reference = " << p3.use_count() << std::endl; // 輸出 1

上面的程式碼實際測試會崩潰。原因在於,使用原始指標num建立了p1,又同樣方法建立了p3,當p1超出作用域時會呼叫delete釋放num記憶體,此時num成了懸空指標,當p3超出作用域再次delete的時候就可能會出錯。

2.5.2不要用棧中的指標構造 shared_ptr 物件

int x = 12;
std::shared_ptr<int> ptr(&x);

上面的程式碼在vs環境下執行會崩潰。原因在於,shared_ptr 預設的建構函式中使用的是delete來刪除關聯的指標,所以構造的時候也必須使用new出來的堆空間的指標。

2.5.3 建議使用make_shared()

auto p1 = make_shared<int>();
std::shared_ptr<int> p2(p1);

3. weak_ptr

3.1 一個迴圈引用的例子

class ClassB;

class ClassA
{
public:
	ClassA() { cout << "ClassA Constructor..." << endl; }
	~ClassA() { cout << "ClassA Destructor..." << endl; }
	shared_ptr<ClassB> pb;  // 在A中引用B
};

class ClassB
{
public:
	ClassB() { cout << "ClassB Constructor..." << endl; }
	~ClassB() { cout << "ClassB Destructor..." << endl; }
	shared_ptr<ClassA> pa;  // 在B中引用A
};

int main()
{
	{
		shared_ptr<ClassA> spa = make_shared<ClassA>();
		shared_ptr<ClassB> spb = make_shared<ClassB>();
		spa->pb = spb;
		spb->pa = spa;
	}

	system("pause");
    return 0;
}

列印結果:

3.2 weak_ptr的概念

weak_ptr是為了配合shared_ptr而引入的一種智慧指標,它指向一個由shared_ptr管理的物件而不影響所指物件的生命週期,也就是將一個weak_ptr繫結到一個shared_ptr不會改變shared_ptr的引用計數。不論是否有weak_ptr指向,一旦最後一個指向物件的shared_ptr被銷燬,物件就會被釋放

3.3 weak_ptr建立

std::shared_ptr<int> sp(new int(5));
cout << "建立前sp的引用計數:" << sp.use_count() << endl;    // 1

std::weak_ptr<int> wp(sp);
cout << "建立後sp的引用計數:" << sp.use_count() << endl;    // 1

3.4判斷weak_ptr指向物件是否存在

C++11中通過lock()函式判斷weak_ptr指向的物件是否存在。如果物件存在,lock()函式返回一個指向共享物件的shared_ptr,否則返回一個空shared_ptr。

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A() 
	{
		cout << "~A()" << endl; 
	}
};

int main()
{
	{
		std::shared_ptr<A> sp(new A());
		std::weak_ptr<A> wp(sp);
		sp.reset();

		std::shared_ptr<A> p1 = wp.lock();
		if (nullptr == p1)
		{
			cout << "wp指向的物件為空" << endl;
		}
	}
	

	system("pause");
    return 0;
}

3.5 weak_ptr的使用

class ClassB;

class ClassA
{
public:
	ClassA() { cout << "ClassA Constructor..." << endl; }
	~ClassA() { cout << "ClassA Destructor..." << endl; }
	std::weak_ptr<ClassB> pb;  // 在A中引用B
};

class ClassB
{
public:
	ClassB() { cout << "ClassB Constructor..." << endl; }
	~ClassB() { cout << "ClassB Destructor..." << endl; }
	std::weak_ptr<ClassA> pa;  // 在B中引用A
};

int main()
{

	{
		shared_ptr<ClassA> spa = make_shared<ClassA>();
		shared_ptr<ClassB> spb = make_shared<ClassB>();
		spa->pb = spb;
		spb->pa = spa;
	}
	

	system("pause");
    return 0;
}

列印結果: