天天看點

C++11智能指針學習(unique_ptr、shared_ptr和weak_ptr)

目錄

        • 前言
        • std::unique_ptr<T>
          • 介紹
          • 使用示例
        • std::shared_ptr<T>
          • 介紹
          • 使用示例
        • std::weak_ptr<T>
          • 介紹
          • 使用示例
        • std::shared_ptr<T>導緻的循環依賴問題
        • 總結
        • 參考文獻

前言

從C++11起,标準庫提供了便于使用的、不同且高效的智能指針的實作。這些智能指針幾乎是沒有bug的,因為在引入标準之前,它們在Boost庫中已經開發了很長一段時間。智能指針減少了記憶體洩漏的可能性,同時,它們被設計為線程安全的。是以有必要學習一下它們的使用,以在資源管理中更好地管理資源,減少記憶體洩漏的可能。

std::unique_ptr<T>

介紹

std::shared_ptr<T>模闆類(定義在<memory>頭檔案中),這個智能指針提供的是獨占的所有權。也就是說,一個對象一次隻能由一個std::unique_ptr<T>執行個體擁有。

使用示例

為友善介紹如何使用,現定義如下類:

A為基類,有一個虛函數和虛析構函數。

B為A的派生類,重寫了show函數。

class A {
public:
	A() {
		std::cout << this << " A::A()" << std::endl;
	}
	virtual ~A() {
		std::cout << this << " A::~A()" << std::endl;
	}
	virtual void show() {
		std::cout << "This is A." << std::endl;
	}
};

class B:public A {
public:
	B() {
		std::cout << this << " B::B()" << std::endl;
	}
	~B() {
		std::cout << this << " B::~B()" << std::endl;
	}
	virtual void show() {
		std::cout << "This is B." << std::endl;
	}
};
           

則,使用方法如下:

#include <iostream>
#include <memory>
class A;
class B;//A B定義見上面

int main()
{
	std::unique_ptr<B> pb = std::make_unique<B>();
	std::unique_ptr<A> pa = std::make_unique<B>();
	pa->show();
	pb->show();
	return 0;
}
           

結果如下:

000361B8 A::A()
000361B8 B::B()
000363C8 A::A()
000363C8 B::B()
This is B.
This is B.
000363C8 B::~B()
000363C8 A::~A()
000361B8 B::~B()
000361B8 A::~A()
           

由此可見,我們通過這個模闆類管理指針,還是可以多态調用。

獨占性見下面示例:

#include <iostream>
#include <memory>
class A;
class B;//A B定義見上面

int main()
{
	std::unique_ptr<B> pb = std::make_unique<B>();
	std::unique_ptr<A> pa = std::make_unique<B>();
	auto pc = pb;//錯誤,無法拷貝
	return 0;
}
           

無法拷貝,但可以用std::move進行移動:

#include <iostream>
#include <memory>
class A;
class B;//A B定義見上面

int main()
{
	std::unique_ptr<B> pb = std::make_unique<B>();
	std::unique_ptr<A> pa = std::make_unique<B>();
	auto pc = std::move(pb);//使用std::move
	std::cout << "pb = " << pb << std::endl;
	std::cout << "pc = " << pc << std::endl;
	return 0;
}
           

結果為:

00616620 A::A()
00616620 B::B()
00616530 A::A()
00616530 B::B()
pb = 00000000
pc = 00616620
00616620 B::~B()
00616620 A::~A()
00616530 B::~B()
00616530 A::~A()
           

std::shared_ptr<T>

介紹

shared_ptr,顧名思義,就是具有共享所有權。std::shared_ptr<T>的執行個體指向T類型的一個對象,并且可以與std::shared_ptr<T>的其他執行個體共享這個所有權。是以T類型的一個執行個體的所有權和删除它的責任,可以有許多共享它的std::shared_ptr<T>執行個體接管。

std::shared_ptr<T>提供了簡單且有限的垃圾回收功能。這個智能指針的内部實作有一個引用計數器,用于監視目前有多少個std::shared_ptr<T>的執行個體。如果智能指針的最後一個執行個體被銷毀,智能指針就會釋放它所有的資源。

使用示例

shared_ptr的使用較為簡單,常與weak_ptr一起使用(見weak_ptr部分)。

#include <iostream>
#include <memory>
class A;
class B;//A B定義見上面

int main()
{
	std::shared_ptr<B> pb = std::make_shared<B>();
	std::shared_ptr<A> pa = pb;
	pa->show();
	pb->show();
	return 0;
}
           

結果為:

008E9E44 A::A()
008E9E44 B::B()
This is B.
This is B.
008E9E44 B::~B()
008E9E44 A::~A()
           

std::weak_ptr<T>

介紹

std::weak_ptr<T>是無所有權但是能夠安全通路的。

使用示例

考慮如下情景:

std::shared_ptr<B> psb = std::make_shared<B>();
B *pb = psb.get();//通過get獲得原始指針
           

以上語句含有安全性問題,因為如果共享的最後一個執行個體在程式某個地方被釋放,那麼這個原始指針仍然在某個地方被使用,則原始指針會變成野指針,因為指針指向的空間已經被釋放了。是以這是十分嚴重的問題。此時就可以考慮使用std::weak_ptr<T>來避免。

示例如下:

#include <iostream>
#include <memory>
class A;
class B;//A B定義見上面

void test(const std::weak_ptr<B> &weakSrc) {
	if (weakSrc.expired()) {//判斷資源是否過期
		std::cout << "資源無效!" << std::endl;
	}
	else {
		std::shared_ptr<B> shareSrc = weakSrc.lock();//通過lock函數擷取shared_ptr執行個體
		shareSrc->show();
	}
}

int main() {
	std::shared_ptr<B> pb = std::make_shared<B>();
	std::weak_ptr<B> pw{ pb };
	
	test(pw);
	pb.reset();//删除pb指向的B類執行個體
	test(pw);
	return 0;
}
           

結果如下:

00F882CC A::A()
00F882CC B::B()
This is B.
00F882CC B::~B()
00F882CC A::~A()
資源無效!
           

std::shared_ptr<T>導緻的循環依賴問題

當std::shared_ptr<T>執行個體内部的引用計數為0時,就釋放所指向的執行個體空間,而當兩個類有循環依賴問題存在時,就會導緻引用永遠不為0,是以就不會被釋放,進而導緻記憶體洩漏。如下:

#include <iostream>
#include <memory>

class B;//前置聲明

class A {
public:
	void setB(std::shared_ptr<B> &pb) {
		this->sharedPb = pb;
	}

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

	~A() {
		std::cout << "A::~A()" << std::endl;
	}
private:
	std::shared_ptr<B> sharedPb;//共享指針
};

class B {
public:
	void setA(std::shared_ptr<A> &pa) {
		this->sharedPa = pa;
	}

	B() {
		std::cout << "B::B()" << std::endl;
	}

	~B() {
		std::cout << "B::~B()" << std::endl;
	}
private:
	std::shared_ptr<A> sharedPa;
};

int main() {
	{//建立塊作用域,用于測試
		auto sharedPa = std::make_shared<A>();
		auto sharedPb = std::make_shared<B>();

		sharedPa->setB(sharedPb);
		sharedPb->setA(sharedPa);
	}
	//此時sharedPa和sharedPb已經無法通路了,但是空間并沒有被釋放,即産生了記憶體洩漏
	return 0;
}
           

結果如下:

A::A()
B::B()
           

而将A類和B類中的std::shared_ptr<T>成員變量類型替換為std::weak_ptr<T>,就能解決記憶體洩漏問題。

如下:

#include <iostream>
#include <memory>

class B;//前置聲明

class A {
public:
	void setB(std::shared_ptr<B> &pb) {
		this->sharedPb = pb;
	}

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

	~A() {
		std::cout << "A::~A()" << std::endl;
	}
private:
	//std::shared_ptr<B> sharedPb;//共享指針
	std::weak_ptr<B> sharedPb;
};

class B {
public:
	void setA(std::shared_ptr<A> &pa) {
		this->sharedPa = pa;
	}

	B() {
		std::cout << "B::B()" << std::endl;
	}

	~B() {
		std::cout << "B::~B()" << std::endl;
	}
private:
	//std::shared_ptr<A> sharedPa;
	std::weak_ptr<A> sharedPa;
};

int main() {
	{//建立塊作用域,用于測試
		auto sharedPa = std::make_shared<A>();
		auto sharedPb = std::make_shared<B>();

		sharedPa->setB(sharedPb);
		sharedPb->setA(sharedPa);
	}
	//此時sharedPa和sharedPb已經無法通路了,同時執行個體對象被釋放,是以解決了記憶體洩漏問題
	return 0;
}
           

結果如下:

A::A()
B::B()
B::~B()
A::~A()
           

總結

  • std::unique_ptr<T>:具有獨占所有權
  • std::shared_ptr<T>:具有共享所有權
  • std::weak_ptr<T>:無所有權但是能夠安全通路

參考文獻

《C++代碼整潔之道-C++17可持續軟體開發模式實踐》

繼續閱讀