加粗樣式>@TOC
智能指針是代理模式的具體應用,它使用 RAII 技術代理了裸指針,能夠自動釋放內存, 無需程序員干預,所以被稱為“智能指針”。
智能指針不是指針,而是一個對象,所以不要對其調用delete,它會自動管理初始化時的指針,在離開作用域時析構釋放內存。
智能指針也沒有定義加減運算,不能隨意移動指針地址,這樣避免了指針越界操作。
在使用上:
如果指針是“獨占”使用,就應該選擇 unique_ptr,它為裸指針添加了很多限制,更加安全 。
如果指針是“共享”使用,就應該選擇 shared_ptr,它的功能非常完善,用法幾乎與原始指針一樣
使用智能指針要加頭文件#include <memory>
工廠函數make_unique()、make_shared()
不只是返回智能指針對象,其內部也有優化。
如果你已經理解了智能指針,就盡量不要再使用裸指針、new 和 delete 來操作內存了。
unique_ptr
unique_ptr需要手動初始化,聲明的時候必須用模板參數指定類型:
unique_ptr<int> ptr1(new int(10)); // int智能指針
assert(*ptr1 = 10); // 可以使用*取內容
assert(ptr1 != nullptr); // 可以判斷是否為空指針
unique_ptr<string> ptr2(new string("hello")); // string智能指針
assert(*ptr2 == "hello"); // 可以使用*取內容
assert(ptr2->size() == 5); // 可以使用->調用成員函數
也可以調用工廠函數,強制創建智能指針的時候必須初始化:
auto ptr3 = make_unique<int>(42); // 工廠函數創建智能指針
assert(ptr3 && *ptr3 == 42);
auto ptr4 = make_unique<string>("god of war"); // 工廠函數創建智能指針
assert(!ptr4->empty());
unique_ptr表示該智能指針的所有權是唯一的,不允許共享,任何時候只能有一個人持有。
它禁止拷貝賦值,但是可以使用std::move()
顯式地聲明所有權轉移:
auto ptr1 = make_unique<int>(42); // 工廠函數創建智能指針
assert(ptr1 && *ptr1 == 42); // 此時智能指針有效
auto ptr2 = ptr1; // 編譯有問題
auto ptr2 = std::move(ptr1); // 使用move()轉移所有權
assert(ptr2 && *ptr2 == 42);
assert(ptr1); // 此時智能指針無效.會報錯
指針的所有權就被轉走了,原來的 unique_ptr 變成了空指針,新的 unique_ptr 接替了管理權,保證所有權的唯一性
shared_ptr
基本使用方式與unique_ptr并無不同:
shared_ptr<int> ptr1(new int(10)); // int智能指針
assert(*ptr1 = 10); // 可以使用*取內容
shared_ptr<string> ptr2(new string("hello")); // string智能指針
assert(*ptr2 == "hello"); // 可以使用*取內容
auto ptr3 = make_shared<int>(42); // 工廠函數創建智能指針
assert(ptr3 && *ptr3 == 42); // 可以判斷是否為空指針
auto ptr4 = make_shared<string>("zelda"); // 工廠函數創建智能指針
assert(!ptr4->empty()); // 可以使用->調用成員函數
不過它的所有權可以被安全共享,支持拷貝賦值
auto ptr1 = make_shared<int>(42); // 工廠函數創建智能指針
assert(ptr1 && ptr1.unique() ); // 此時智能指針有效且唯一
auto ptr2 = ptr1; // 直接拷貝賦值,不需要使用move()
assert(ptr1 && ptr2); // 此時兩個智能指針均有效
assert(ptr1 == ptr2); // shared_ptr可以直接比較
// 兩個智能指針均不唯一,且引用計數為2
assert(!ptr1.unique() && ptr1.use_count() == 2);
assert(!ptr2.unique() && ptr2.use_count() == 2);
其內部使用引用計數,所以具有完整的”值語義“,可以在任何場合下代替原始指針。
不過維護引用計數的存儲和管理都是成本,過度使用會降低運行效率。其引用計數也會帶來循環引用,下面是簡化后的典型例子:
#include <iostream>
#include <memory>
#include <assert.h>
using namespace std;
class Node final {
public:using this_type = Node;using shared_type = std::shared_ptr<this_type>;shared_type next; // 使用只能指針來指向下一個節點
};
int main() {auto n1 = make_shared<Node>(); // 工廠函數創建智能指針auto n2 = make_shared<Node>();// 此時引用計數均為1assert(n1.use_count() == 1);assert(n2.use_count() == 1);// 產生循環引用n1->next = n2;n2->next = n1;// 此時引用計數均為2,且無法減到0,內存泄露assert(n1.use_count() == 2);assert(n2.use_count() == 2);
}
這個例子很簡單,你一下子就能看出存在循環引用。但在實際開發中,指針的關系可不像例 子那么清晰,很有可能會不知不覺形成一個鏈條很長的循環引用,復雜到你根本無法識別, 想要找出來基本上是不可能的。 想要從根本上杜絕循環引用,光靠 shared_ptr 是不行了,必須要用到weak_ptr
。
weak_ptr
它專門為打破循環引用而設計,只觀察指針,不會增 加引用計數(弱引用),但在需要的時候,可以調用成員函數 lock(),獲取 shared_ptr(強引用) 。用法如下
#include <iostream>
#include <memory>
#include <assert.h>
using namespace std;
class Node final {
public:using this_type = Node;// 改用weak_ptrusing shared_type = std::weak_ptr<this_type>;shared_type next; // 使用只能指針來指向下一個節點
};
int main() {auto n1 = make_shared<Node>(); // 工廠函數創建智能指針auto n2 = make_shared<Node>();// 此時引用計數均為1assert(n1.use_count() == 1);assert(n2.use_count() == 1);// 產生循環引用n1->next = n2;n2->next = n1;// 因為使用了weak_ptr,引用計數為1assert(n1.use_count() == 1);assert(n2.use_count() == 1);if (!n1->next.expired()) { // 檢查指針是否有效auto ptr = n1->next.lock(); // lock()獲取shared_ptrassert(ptr == n2);}
}