C++語言標準庫STL提供了一套智能指針接口,且一直處於完善狀態,如下圖所示:
最早的智能指針是std::auto_ptr,到c++11才開始廣泛使用,平時用得最多的是這三個:
其中unique_ptr只能指向一個對象,shared_ptr是通用的智能指針,weak_ptr可以理解為只讀用途的指針,這種指針不改變引用計數。
在舊式的C++程序開發過程中,我們會比較習慣用傳統的裸指針來管理資源,通過成對使用new和delete來保證內存安全,這種做法不會造成太大問題,只是在某些情況下會出現內存難於管理的局面。最典型的是存在分支的情況,如下圖所示:
void test()
{
auto p = new std::string();
if(...){
delete p;
return;
}
...
delete p;
}每一處返回,都需要刪除指針p,再來看看智能指針的寫法:
void test()
{
auto p = std::make_unique<std::string>();
if(...){
return;
}
...
}這樣採用對象的方式來管理內存,通過對象的生命周期來自動釋放內存。
使用unique_ptr和share_ptr目前智能指針主要使用unique_ptr和share_ptr,兩者的區別如下:
std::shared_ptr 是一種智能指針,它能夠記錄多少個 shared_ptr 指向同一個對象,當引用計數變為零的時候對象會自動執行delete。
std::make_shared 能夠用來消除顯式的使用 new, 並返回這個對象類型的std::shared_ptr指針。例如:
auto pointer = std::make_shared<int>(10);std::unique_ptr 是一種獨佔的智能指針,它禁止其他智能指針與其共享同一個對象,從而保證代碼的安全,如下所示:
std::unique_ptr<int> pointer = std::make_unique<int>(10);
std::unique_ptr<int> pointer2 = pointer; // 非法獨佔也就是不可複製,但是,我們可以利用 std::move 函數將其轉移給其他的 unique_ptr。
弱弱的std::weak_ptr還有一個智能指針是std::weak_ptr,這個智能指針主要是針對std::shared_ptr的不足而設計的。如下所示:
struct A;
struct B;
struct A {
std::shared_ptr<B> pointer;
~A() {
std::cout << "A 被銷毀" << std::endl;
}
};
struct B {
std::shared_ptr<A> pointer;
~B() {
std::cout << "B 被銷毀" << std::endl;
}
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;
}a和b都引用了對方,導致計數都為2,當a和b離開作用域時,引用計數會是1,也就不能釋放內存資源。
解決這個問題的辦法就是使用弱引用指針 std::weak_ptr,std::weak_ptr是一種弱引用(相比較而言 std::shared_ptr 就是一種強引用)。弱引用不會引起引用計數增加,當換用弱引用時候,最終的釋放流程如下圖所示:
std::weak_ptr 沒有 * 運算符和 -> 運算符,所以不能夠對資源進行操作,它的唯一作用就是用於檢查 std::shared_ptr 是否存在,其 expired() 方法能在資源未被釋放時,會返回 true,否則返回 false。
線程安全的智能指針智能指針的線程安全需要考慮2個問題,一個是多線程狀態下智能指針內部的引用計數是否是線程安全的,另外一個問題是多線程中智能指針指向的值的讀寫是否是線程安全的。
對於前一個問題,C++標準庫聲明shared_ptr是線程安全的,不用擔心引用次數出問題,也不用擔心析構函數會調用多次。
後一個問題其實是對shared_ptr對象的寫入,這個不是線程安全的,可以通過atomic_store和atomic_load來處理安全讀寫。
一般而言,如果一個智能指針在初次賦值後,後續不需要寫入操作,那麼所有的讀取操作都是線程安全的。而如果後續需要改變這個指針的指向,那麼就需要加鎖。
總結智能指針這種技術並不新奇,在很多語言中都是一種常見的技術,現代 C++ 將這項技術引進,在一定程度上消除了 new/delete 的濫用,是一種更加成熟的編程範式。