介紹 C++ 的智能指針 (Smart Pointers) 相關 API。ios
C++ 中的智能指針是爲了解決內存泄漏、重複釋放等問題而提出的,它基於 RAII (Resource Acquisition Is Initialization),也稱爲「資源獲取即初始化」 的思想實現。智能指針實質上是一個類,但通過封裝以後,在行爲語義上的表現像指針。數組
參考資料:安全
shared_ptr
可以記錄多少個 shared_ptr
共同指向一個對象,從而消除顯式調用 delete
,當引用計數變爲零的時候就會將對象自動刪除。多線程
注意,這裏使用 shared_ptr
可以實現自動 delete
,可是若是使用以前仍須要 new
的話,代碼風格就會變得很「奇怪」,由於 new/delete
老是須要成對出現的,因此儘量使用封裝後的 make_shared
來「代替」new
。ide
shared_ptr
基於引用計數實現,每個 shared_ptr
的拷貝均指向相同的內存。若是某個 shared_ptr
被析構(生命週期結束),那麼引用計數減 1 ,當引用計數爲 0 時,自動釋放指向的內存。函數
shared
的全部成員函數,包括拷貝構造函數 (Copy Constructor) 和拷貝賦值運算 (Copy Assignment Operator),都是線程安全的,即便這些 shared_ptr
指向同一對象。但若是是多線程訪問同一個 non-const 的 shared_ptr
,那有可能發生資源競爭 (Data Race) 的狀況,好比改變這個 shared_ptr
的指向,所以這種狀況須要實現多線程同步機制。固然,可使用 shared_ptr overloads of atomic functions 來防止 Data Race 的發生。ui
以下圖所示,shared_ptr
內部僅包括 2 個指針,一個指針指向共享對象,另一個指針指向 Control block .this
下面是正確的方式。atom
void func1() { int *a = new int[10]; shared_ptr<int[]> p(a); // a is same as p.get() cout << a << endl; cout << p.get() << endl; for (int i = 0; i < 10; i++) p[i] = i; for (int i = 0; i < 10; i++) cout << a[i] << ' '; } // Output: 1-9
下面是錯誤的方式,由於 ptr
析構時會釋放 &a
這個地址,但這個地址在棧上(而不是堆),所以會發生運行時錯誤。spa
int main() { int a = 10; shared_ptr<int> ptr(&a); // a is same as p.get(), but runs fail cout << &a << endl; cout << ptr.get() << endl; }
nullptr
初始化,那麼引用計數的初始值爲 0 而不是 1 。shared_ptr<void *> p(nullptr); cout << p.use_count() << endl;
shared_ptr
。int main() { int *p = new int[10]; shared_ptr<int> ptr1(p); shared_ptr<int> ptr2(p); cout << p << endl; cout << ptr1.get() << endl; cout << ptr2.get() << endl; }
上述方式是錯誤的。能夠經過編譯,三行 cout
也能正常輸出,但會發生運行時錯誤,由於 ptr2
會先執行析構函數,釋放 p
,而後 ptr1
進行析構的時候,就會對無效指針 p
進行重複釋放。
0x7feefd405a10 0x7feefd405a10 0x7feefd405a10 a.out(6286,0x113edde00) malloc: *** error for object 0x7feefd405a10: pointer being freed was not allocated a.out(6286,0x113edde00) malloc: *** set a breakpoint in malloc_error_break to debug
make_shared
初始化make_shared
的參數能夠時一個對象,也能夠是跟該類的構造函數匹配的參數列表。
auto ptr1 = make_shared<vector<int>>(10, -1); auto ptr2 = make_shared<vector<int>>(vector<int>(10, -1));
與經過構造函數初始化不一樣的是,make_shared
容許傳入一個臨時對象,如如下代碼:
int main() { vector<int> v = {1, 2, 3}; auto ptr = make_shared<vector<int>>(v); // &v = 0x7ffeef698690 // ptr.get() = 0x7fc03ec05a18 cout << &v << endl; cout << ptr.get() << endl; // v[0] is still 1 ptr.get()->resize(3, -1); cout << v[0] << endl; }
經過 ptr.get()
獲取指針並修改指向的內存,並不會影響局部變量 v
的內容。
在初始化時傳入一個函數指針,shared_ptr
在釋放指向的對象時,會調用自定義的 deleter
處理釋放行爲。
int main() { int *p = new int[10]; auto func = [](int *p) { delete[] p; cout << "Delete memory at " << p << endl; }; shared_ptr<int> ptr(p, func); }
那麼 deleter
有什麼用呢?假如咱們有這麼一段代碼:
class Basic { public: Basic() { cout << "Basic" << endl; } ~Basic() { cout << "~Basic" << endl; } }; int main() { Basic *p = new Basic[3]; shared_ptr<Basic> ptr(p); }
這段代碼會發生運行時錯誤。由於 shared_ptr
默認是使用 delete
去釋放指向的對象,但定義了析構函數的對象數組,必需要經過 delete[]
析構,不然產生內存錯誤。
所以,爲了使上述代碼正常工做,須要自定義 delete
函數:
shared_ptr<Basic> ptr(p, [](Basic *p){ delete[] p; });
或者(C++17 及其以後的標準支持):
shared_ptr<Base[]> ptr(p);
根據參考資料 [1] ,shared_ptr
指向一個函數,有時用於保持動態庫或插件加載,只要其任何函數被 shared_ptr
引用:
void func() { cout << "hello" << endl; } int main() { shared_ptr<void()> ptr(func, [](void (*)()) {}); (*ptr)(); }
注意,這裏自定義的 deleter
是必不可少的,不然不能經過編譯。
#include <iostream> #include <memory> #include <thread> #include <chrono> #include <mutex> using namespace std; class Base { public: Base() { cout << "Base" << endl; } ~Base() { cout << "~Base" << endl; } }; class Derived : public Base { public: Derived() { cout << " Derived" << endl; } ~Derived() { cout << " ~Derived" << endl; } }; void worker(shared_ptr<Base> ptr) { this_thread::sleep_for(std::chrono::seconds(1)); shared_ptr<Base> lp = ptr; { static std::mutex io_mutex; lock_guard<mutex> lock(io_mutex); cout << "local pointer in a thread:\n" << " lp.get() = " << lp.get() << ", " << " lp.use_count() = " << lp.use_count() << "\n"; } } int main() { shared_ptr<Base> ptr = make_shared<Derived>(); cout << "Created a shared Derived (as a pointer to Base)\n" << "ptr.get() = " << ptr.get() << ", " << "ptr.use_count() = " << ptr.use_count() << '\n'; thread t1(worker, ptr), t2(worker, ptr), t3(worker, ptr); this_thread::sleep_for(std::chrono::seconds(2)); ptr.reset(); std::cout << "Shared ownership between 3 threads and released\n" << "ownership from main:\n" << " p.get() = " << ptr.get() << ", p.use_count() = " << ptr.use_count() << '\n'; t1.join(), t2.join(), t3.join(); }
輸出:
Base Derived Created a shared Derived (as a pointer to Base) ptr.get() = 0x7fcabc405a08, ptr.use_count() = 1 Shared ownership between 3 threads and released ownership from main: p.get() = 0x0, p.use_count() = 0 local pointer in a thread: lp.get() = 0x7fcabc405a08, lp.use_count() = 6 local pointer in a thread: lp.get() = 0x7fcabc405a08, lp.use_count() = 4 local pointer in a thread: lp.get() = 0x7fcabc405a08, lp.use_count() = 2 ~Derived ~Base
lp.use_count
也多是 {5,3,2}
這樣的序列。在 worker
傳入參數過程當中,ptr
被拷貝了 3 次,而且在進入 worker
後,三個線程的局部變量 lp
又把 ptr
拷貝了 3 次,所以 user_count
的最大值是 7 。
unique_ptr
保證同一時刻只能有一個 unique_ptr
指向給定對象。發生下列狀況之一時,指定對象就會被釋放:
unique_ptr
被銷燬(生命週期消亡,被 delete
等狀況)unique_ptr
調用 reset
或者進行 ptr1 = move(ptr2)
操做基於這 2 個特色,non-const 的 unique_ptr
能夠把管理對象的全部權轉移給另一個 unique_ptr
:
示例代碼:
class Base { public: Base() { cout << "Base" << endl; } ~Base() { cout << "~Base" << endl; } }; int main() { auto p = new Base(); cout << p << endl; unique_ptr<Base> ptr(p); unique_ptr<Base> ptr2 = std::move(ptr); cout << ptr.get() << endl; cout << ptr2.get() << endl; } /* Output is : Base 0x7fd81fc059f0 0x0 0x7fd81fc059f0 ~Base */
在上述代碼中,存在 U = move(V)
,當執行該語句時,會發生兩件事情。首先,當前 U 所擁有的任何對象都將被刪除;其次,指針 V 放棄了原有的對象全部權,被置爲空,而 U 則得到轉移的全部權,繼續控制以前由 V 所擁有的對象。
若是是 const unique_ptr
,那麼其指向的對象的做用域 (Scope) 只能侷限在這個 const unique_ptr
的做用域當中。
此外,unique_ptr
不能經過 pass by value 的方式傳遞參數,只能經過 pass by reference 或者 std::move
。
與 shared_ptr
相似。但因爲 unique_ptr
的特色,它沒有拷貝構造函數,所以不容許 unique_ptr<int> ptr2 = ptr
這樣的操做。
下面是 unique_ptr
正確初始化的例子。
class Base { public: Base() { cout << "Base" << endl; } ~Base() { cout << "~Base" << endl; } void printThis() { cout << this << endl; } }; int main() { auto p = new Base(); unique_ptr<Base> ptr(p); ptr->printThis(); } /* Output is: Base 0x7fbe0a4059f0 ~Base */
int main() { auto p = new Base[3]; unique_ptr<Base[]> ptr(p); for (int i = 0; i < 3; i++) ptr[i].printThis(); } /* Output is: Base * 3 0xc18c28 0xc18c29 0xc18c2a ~Base * 3 */
make_unique
與 make_shared
相似,容許向 make_unique
傳入一個臨時變量。
void func3() { auto ptr = make_unique<vector<int>>(5, 0); for (int i = 0; i < 5;i++) (*ptr)[i] = i; for (int x : *ptr) cout << x << ' '; } // Output: 0 1 2 3 4
unique_ptr
的 deleter
與 shared_ptr
不一樣,它是基於模版參數實現的。
使用仿函數
struct MyDeleter { void operator()(Base *p) { cout << "Delete memory[] at " << p << endl; delete[] p; } }; unique_ptr<Base[], MyDeleter> ptr(new Base[3]); // unique_ptr<Base, MyDeleter> ptr(new Base[3]); // both of them is okay
使用普通函數
unique_ptr<Base[], void (*)(Base * p)> ptr(new Base[3], [](Base *p) { cout << "Delete memory[] at " << p << endl; delete[] p; });
使用 std::function
unique_ptr<Base[], function<void(Base *)>> ptr(new Base[3], [](Base *p) { delete[] p; });
注意到,使用普通函數時,模版參數爲 void (*)(Base *p)
,這是一種數據類型,該類型是一個指針,指向一個返回值爲 void
, 參數列表爲 (Base *p)
的函數,而 void *(Base *p)
則是在聲明一個函數(看不懂能夠忽略)。
unique_ptr
做爲函數參數,只能經過引用,或者 move
操做實現。
下列操做沒法經過編譯:
void func5(unique_ptr<Base> ptr) {} int main() { unique_ptr<Base> ptr(new Base()); func5(ptr); }
須要改爲:
void func5(unique_ptr<Base> &ptr) {} func(ptr);
或者經過 move
轉換爲右值引用:
void func5(unique_ptr<Base> ptr) { cout << "ptr in function: " << ptr.get() << endl; } int main() { auto p = new Base(); cout << "p = " << p << endl; unique_ptr<Base> ptr(p); func5(move(ptr)); cout << "ptr in main: " << ptr.get() << endl; } /* Output is: Base p = 0xa66c20 ptr in function: 0xa66c20 ~Base ptr in main: 0 */
把 unique_ptr
做爲函數返回值,會自動發生 U = move(V)
的操做(轉換爲右值引用):
unique_ptr<Base> func6() { auto p = new Base(); unique_ptr<Base> ptr(p); cout << "In function: " << ptr.get() << endl; return ptr; } int main() { auto ptr = func6(); cout << "In main: " << ptr.get() << endl; }
函數 | 做用 |
---|---|
release | returns a pointer to the managed object and releases the ownership (will not delete the object) |
reset | replaces the managed object (it will delete the object) |
swap | swaps the managed objects |
get | returns a pointer to the managed object |
get_deleter | returns the deleter that is used for destruction of the managed object |
operator bool | checks if there is an associated managed object (more details) |
operator = | assigns the unique_ptr , support U = move(V) , U will delete its own object |
#include <vector> #include <memory> #include <iostream> #include <fstream> #include <functional> #include <cassert> #include <cstdio> using namespace std; // helper class for runtime polymorphism demo class B { public: virtual void bar() { cout << "B::bar\n"; } virtual ~B() = default; }; class D : public B { public: D() { cout << "D::D\n"; } ~D() { cout << "D::~D\n"; } void bar() override { cout << "D::bar\n"; } }; // a function consuming a unique_ptr can take it by value or by rvalue reference unique_ptr<D> passThrough(unique_ptr<D> p) { p->bar(); return p; } // helper function for the custom deleter demo below void close_file(FILE *fp) { std::fclose(fp); } // unique_ptr-base linked list demo class List { public: struct Node { int data; unique_ptr<Node> next; Node(int val) : data(val), next(nullptr) {} }; List() : head(nullptr) {} ~List() { while (head) head = move(head->next); } void push(int x) { auto t = make_unique<Node>(x); if (head) t->next = move(head); head = move(t); } private: unique_ptr<Node> head; }; int main() { cout << "unique ownership semantics demo\n"; { auto p = make_unique<D>(); auto q = passThrough(move(p)); assert(!p), assert(q); } cout << "Runtime polymorphism demo\n"; { unique_ptr<B> p = make_unique<D>(); p->bar(); cout << "----\n"; vector<unique_ptr<B>> v; v.push_back(make_unique<D>()); v.push_back(move(p)); v.emplace_back(new D()); for (auto &p : v) p->bar(); } cout << "Custom deleter demo\n"; ofstream("demo.txt") << "x"; { unique_ptr<FILE, decltype(&close_file)> fp(fopen("demo.txt", "r"), &close_file); if (fp) cout << (char)fgetc(fp.get()) << '\n'; } cout << "Linked list demo\n"; { List list; for (long n = 0; n != 1000000; ++n) list.push(n); cout << "Pass!\n"; } }
weak_ptr
指針一般不單獨使用(由於沒有實際用處),只能和 shared_ptr
類型指針搭配使用。
當 weak_ptr
類型指針的指向和某一 shared_ptr
指針相同時,weak_ptr
指針並不會使所指堆內存的引用計數加 1;一樣,當 weak_ptr
指針被釋放時,以前所指堆內存的引用計數也不會所以而減 1。也就是說,weak_ptr
類型指針並不會影響所指堆內存空間的引用計數。
此外,weak_ptr
沒有重載 *
和 ->
運算符,所以 weak_ptr
只能訪問所指的堆內存,而沒法修改它。
weak_ptr
做爲一個 Observer 的角色存在,能夠獲取 shared_ptr
的引用計數,能夠讀取 shared_ptr
指向的對象。
成員函數:
函數 | 做用 |
---|---|
operator = | weak_ptr 能夠直接被 weak_ptr 或者 shared_ptr 類型指針賦值 |
swap | 與另一個 weak_ptr 交換 own objetc |
reset | 置爲 nullptr |
use_count | 查看與 weak_ptr 指向相同對象的 shared_ptr 的數量 |
expired | 判斷當前 weak_ptr 是否失效(指針爲空,或者指向的堆內存已經被釋放) |
lock | 若是 weak_ptr 失效,則該函數會返回一個空的 shared_ptr 指針;反之,該函數返回一個和當前 weak_ptr 指向相同的 shared_ptr 指針。 |
例子:
#include <memory> #include <iostream> using namespace std; // global weak ptr weak_ptr<int> gw; void observe() { cout << "use count = " << gw.use_count() << ": "; if (auto spt = gw.lock()) cout << *spt << "\n"; else cout << "gw is expired\n"; } int main() { { auto sp = make_shared<int>(233); gw = sp; observe(); } observe(); } // Output: // use count = 1: 233 // use count = 0: gw is expired
使用智能指針的幾個重要原則是:
shared_ptr
要注意避免循環引用