目錄
-
-
-
- 前言
- 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可持續軟體開發模式實踐》