C++ 11 模板庫的 <memory> 頭文件中定義的智能指針,即 shared_ptr 模板類,用來管理指針的存儲,提供有限的內存回收函數,可同時與其他對象共享該管理功能,從而幫助徹底消除內存泄漏和懸空指針的問題。
shared_ptr 類型的對象能夠獲得指針的所有權並共享該所有權:一旦他們獲得所有權,指針的所有者組就會在最后一個釋放該所有權時負責刪除該指針。
shared_ptr 對象一旦它們自己被銷毀,或者它們的值因賦值操作或顯式調用 shared_ptr::reset 而改變時,就會釋放它們共同擁有的對象的所有權。一旦通過指針共享所有權的所有 shared_ptr 對象都釋放了該所有權,則刪除托管對象(通常通過調用 ::delete,也可以在構造時指定不同的刪除器)。
- 同一個shared_ptr被多個線程讀,是線程安全的;
- 同一個shared_ptr被多個線程寫,不是線程安全的;
- 共享引用計數的不同的shared_ptr被多個線程寫,是線程安全的。
shared_ptr 對象只能通過復制它們的值來共享所有權:如果兩個 shared_ptr 是從同一個(非shared_ptr)指針構造(或制造)的,它們都將擁有該指針而不共享它,當其中一個釋放時會導致潛在的訪問問題它(刪除其托管對象)並將另一個指向無效位置。
此外,shared_ptr 對象可以共享一個指針的所有權,同時指向另一個對象。這種能力被稱為別名(參見構造函數),通常用於在擁有成員對象時指向成員對象。因此,一個 shared_ptr 可能與兩個指針相關:
1)一個存儲的指針,即它所指向的指針,以及它用 operator* 取消引用的指針。
2)一個所有者的指針(可能是共享的),它是所有權組負責在某個時間點刪除的指針,並計為使用。
通常,存儲指針和所有者指針指向同一個對象,但別名 shared_ptr 對象(使用別名構造函數及其副本構造的對象)可能指向不同的對象。不擁有任何指針的 shared_ptr 稱為null shared_ptr。不指向任何對象的 shared_ptr 稱為null shared_ptr 並且不應取消引用。請注意,空的 shared_ptr 不一定是null shared_ptr,null shared_ptr 也不一定是空的 shared_ptr。shared_ptr 對象通過提供對它們通過運算符 * 和 -> 指向的對象的訪問來復制有限的指針功能。出於安全原因,它們不支持指針算術。類似weak_ptr,能夠與 shared_ptr 對象共享指針,而無需擁有它們。shared_ptr 有以下成員函數:
(1)構造函數
shared_ptr的構造函數根據使用的參數類型構造 shared_ptr 對象:
1) 默認構造函數:constexpr shared_ptr() noexcept;
2) 從空指針構造:constexpr shared_ptr(nullptr_t) : shared_ptr() {}
3) 從指針構造:template <class U> explicit shared_ptr (U* p);
4) 從指針 + 刪除器構造:template <class U, class D> shared_ptr (U* p, D del); template <class D> shared_ptr (nullptr_t p, D del);
5) 從指針 + 刪除器 + 分配器構造:template <class U, class D, class Alloc> shared_ptr (U* p, D del, Alloc alloc); template <class D, class Alloc> shared_ptr (nullptr_t p, D del, Alloc alloc);
6) 復制構造函數:shared_ptr (const shared_ptr& x) noexcept; template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;
7) 從weak_ptr 復制:template <class U> explicit shared_ptr (const weak_ptr<U>& x);
8) 移動構造函數:shared_ptr (shared_ptr&& x) noexcept; template <class U> shared_ptr (shared_ptr<U>&& x) noexcept;
9) 從其他類型的托管指針移動:template <class U> shared_ptr (auto_ptr<U>&& x); template <class U, class D> shared_ptr (unique_ptr<U,D>&& x);
10) 別名構造函數:template <class U> shared_ptr (const shared_ptr<U>& x, element_type* p) noexcept;
默認構造函數 1) 和 2)對象為空(不擁有指針,使用計數為零)。從指針構造3)該對象擁有 p,將使用計數設置為 1。從指針 + 刪除器構造 4)與 3) 相同,但該對象還擁有刪除器 del 的所有權(並在某些時候需要刪除 p 時使用它)。從指針 + 刪除器 + 分配器構造 5)與 4) 相同,但內部使用所需的任何內存都是使用 alloc 分配的(對象保留一份副本,但不取得所有權)。復制構造函數 6)如果 x 不為空,則對象共享 x 資產的所有權並增加使用次數。如果 x 為空,則構造一個空對象(如同默認構造)。從weak_ptr 7) 復制同上6),除了如果 x 已經過期,則拋出 bad_weak_ptr 異常。移動構造函數 8)該對象獲取由 x 管理的內容,包括其擁有的指針。 x 變成一個空對象(就像默認構造的一樣)。從其他類型的托管指針移動 9)對象獲取由 x 管理的內容並將使用計數設置為 1。放棄的對象變為空,自動失去指針的所有權。別名構造函數 10)同6),除了存儲的指針是p。該對象不擁有 p,也不會管理其存儲。相反,它共同擁有 x 的托管對象並算作 x 的一種額外使用。它還將在發布時刪除 x 的指針(而不是 p)。它可以用來指向已經被管理的對象的成員。
1) p: 其所有權被對象接管的指針。此指針值不應已由任何其他托管指針管理(即,此值不應來自托管指針上的調用成員 get)。U* 應隱式轉換為 T*(其中 T 是 shared_ptr 的模板參數)。
2) del: 用於釋放擁有的對象的刪除器對象。這應該是一個可調用對象,將指向 T 的指針作為其函數調用的參數(其中 T 是 shared_ptr 的模板參數)。
3) alloc:用於分配/取消分配內部存儲的分配器對象。
4) X: 托管指針類型的對象。U* 應隱式轉換為 T*(其中 T 是 shared_ptr 的模板參數)。
#include <iostream> #include <memory> struct C {int* data;}; int main () { std::shared_ptr<int> p1; std::shared_ptr<int> p2 (nullptr); std::shared_ptr<int> p3 (new int); std::shared_ptr<int> p4 (new int, std::default_delete<int>()); std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>()); std::shared_ptr<int> p6 (p5); std::shared_ptr<int> p7 (std::move(p6)); std::shared_ptr<int> p8 (std::unique_ptr<int>(new int)); std::shared_ptr<C> obj (new C); std::shared_ptr<int> p9 (obj, obj->data); std::cout << "use_count:\n"; std::cout << "p1: " << p1.use_count() << '\n'; std::cout << "p2: " << p2.use_count() << '\n'; std::cout << "p3: " << p3.use_count() << '\n'; std::cout << "p4: " << p4.use_count() << '\n'; std::cout << "p5: " << p5.use_count() << '\n'; std::cout << "p6: " << p6.use_count() << '\n'; std::cout << "p7: " << p7.use_count() << '\n'; std::cout << "p8: " << p8.use_count() << '\n'; std::cout << "p9: " << p9.use_count() << '\n'; return 0; }
(2)析構函數
析構函數的作用是銷毀shared_ptr對象。 但是,在此之前,根據成員 use_count 的值,它可能會產生以下副作用:
1)如果 use_count 大於 1(即該對象與其他 shared_ptr 對象共享其托管對象的所有權):與其共享所有權的其他對象的使用計數減 1。
2)如果 use_count 為 1(即對象是托管指針的唯一所有者):刪除其擁有指針所指向的對象(如果 shared_ptr 對象是用特定的刪除器構造的,則調用此函數;否則,函數使用運算符 刪除)。
3)如果 use_count 為零(即對象為空),則該析構函數沒有副作用。
用法舉例:
#include <iostream> #include <memory> int main() { auto deleter = [](int* p) { std::cout << "[deleter called]\n"; delete p; }; std::shared_ptr<int> foo(new int, deleter); std::cout << "use_count: " << foo.use_count() << '\n'; return 0; // [deleter called] }
(3)賦值運算“=”
1) 復制:shared_ptr& operator= (const shared_ptr& x) noexcept; template <class U> shared_ptr& operator= (const shared_ptr<U>& x) noexcept;
2) 移動:shared_ptr& operator= (shared_ptr&& x) noexcept; template <class U> shared_ptr& operator= (shared_ptr<U>&& x) noexcept;
3) 從...移動:template <class U> shared_ptr& operator= (auto_ptr<U>&& x); template <class U, class D> shared_ptr& operator= (unique_ptr<U,D>&& x);
復制分配1) 將對象添加為 x 資產的共享所有者,從而增加它們的 use_count。移動分配 2) 將所有權從 x 轉移到 shared_ptr 對象而不改變 use_count。 x 變成一個空的 shared_ptr(就像默認構造的一樣)。同樣,來自其他托管指針類型 3) 的移動分配也會轉移所有權,並使用 set a use count of 1 進行初始化。
此外,在上述所有情況下,對該函數的調用與在其值更改之前調用了 shared_ptr 的析構函數具有相同的副作用(如果此 shared_ptr 是唯一的,則包括刪除托管對象)。不能將指針的值直接分配給 shared_ptr 對象。您可以改用 make_shared 或成員重置。
托管指針類型的對象。U* 應隱式轉換為 T*(其中 T 是 shared_ptr 的模板參數)。
用法舉例:
#include <iostream> #include <memory> int main () { std::shared_ptr<int> foo; std::shared_ptr<int> bar (new int(10)); foo = bar; // copy bar = std::make_shared<int> (20); // move std::unique_ptr<int> unique (new int(30)); foo = std::move(unique); // move from unique_ptr std::cout << "*foo: " << *foo << '\n'; std::cout << "*bar: " << *bar << '\n'; return 0; }
(4)swap函數
函數聲明:void swap (shared_ptr& x) noexcept; 參數x: 另一個相同類型的 shared_ptr 對象(即,具有相同的類模板參數 T)。作用是將 shared_ptr 對象的內容與 x 的內容交換,在它們之間轉移任何托管對象的所有權,而不會破壞或改變兩者的使用計數。
用法舉例:
#include <iostream> #include <memory> int main () { std::shared_ptr<int> foo (new int(10)); std::shared_ptr<int> bar (new int(20)); foo.swap(bar); std::cout << "*foo: " << *foo << '\n'; std::cout << "*bar: " << *bar << '\n'; return 0; }
(5)reset函數
重置shared_ptr,對於聲明1) 對象變為空(如同默認構造)。在所有其他情況下,shared_ptr 以使用計數為 1 獲取 p 的所有權,並且 - 可選地 - 使用 del 和/或 alloc 作為刪除器 和分配器。另外,調用這個函數有同樣的副作用,就像在它的值改變之前調用了shared_ptr 的析構函數一樣(包括刪除托管對象,如果這個shared_ptr 是唯一的)。
1) void reset() noexcept;
2) template <class U> void reset (U* p);
3) template <class U, class D> void reset (U* p, D del);
4) template <class U, class D, class Alloc> void reset (U* p, D del, Alloc alloc);
用法舉例:
#include <iostream> #include <memory> int main () { std::shared_ptr<int> sp; // empty sp.reset (new int); // takes ownership of pointer *sp=10; std::cout << *sp << '\n'; sp.reset (new int); // deletes managed object, acquires new pointer *sp=20; std::cout << *sp << '\n'; sp.reset(); // deletes managed object return 0; }
(6)get函數
函數聲明:element_type* get() const noexcept; get()返回存儲的指針。存儲的指針指向shared_ptr對象解引用的對象,一般與其擁有的指針相同。存儲的指針(即這個函數返回的指針)可能不是擁有的指針(即對象銷毀時刪除的指針)如果 shared_ptr 對象是別名(即,別名構造的對象及其副本)。
用法舉例:
#include <iostream> #include <memory> int main () { int* p = new int (10); std::shared_ptr<int> a (p); if (a.get()==p) std::cout << "a and p point to the same location\n"; // three ways of accessing the same address: std::cout << *a.get() << "\n"; std::cout << *a << "\n"; std::cout << *p << "\n"; return 0; }
(7)取對象運算“*”
函數聲明:element_type& operator*() const noexcept; 取消引用對象。返回對存儲指針指向的對象的引用。等價於:*get()。如果shared_ptr的模板參數為void,則該成員函數是否定義取決於平台和編譯器,以及它的返回類型 在這種情況下。
用法舉例:
#include <iostream> #include <memory> int main() { std::shared_ptr<int> foo(new int); std::shared_ptr<int> bar(new int(100)); *foo = *bar * 2; std::cout << "foo: " << *foo << '\n'; std::cout << "bar: " << *bar << '\n'; return 0; }
(8)“->”操作符
函數聲明:element_type* operator->() const noexcept; 取消引用對象成員。返回一個指向存儲指針指向的對象的指針,以便訪問其成員之一。如果存儲的指針是空指針,則不應調用該成員函數,它返回與 get() 相同的值。
用法舉例:
#include <iostream> #include <memory> struct C { int a; int b; }; int main() { std::shared_ptr<C> foo; std::shared_ptr<C> bar(new C); foo = bar; foo->a = 10; bar->b = 20; if (foo) std::cout << "foo: " << foo->a << ' ' << foo->b << '\n'; if (bar) std::cout << "bar: " << bar->a << ' ' << bar->b << '\n'; return 0; }
(9)use_count函數
函數聲明:long int use_count() const noexcept; use_count 返回與此對象(包括它)在同一指針上共享所有權的 shared_ptr 對象的數量。如果這是一個空的 shared_ptr,則該函數返回零。庫實現不需要保留任何特定所有者集的計數 ,因此調用此函數可能效率不高。 要具體檢查 use_count 是否為 1,也可以使用 member unique 代替,這樣可能更快。
(10)unique函數
函數聲明:bool unique() const noexcept; 檢查是否唯一 返回 shared_ptr 對象是否不與其他 shared_ptr 對象共享其指針的所有權(即,它是唯一的)。 空指針從來都不是唯一的(因為它們不擁有任何指針)。 如果唯一的 shared_ptr 對象釋放此所有權,則它們負責刪除其托管對象(請參閱析構函數)。 此函數應返回與 (use_count()==1) 相同的值,盡管它可能以更有效的方式執行此操作。 如果這是唯一的 shared_ptr,則返回值 true,否則返回 false。
用法舉例:
#include <iostream> #include <memory> int main() { std::shared_ptr<int> foo; std::shared_ptr<int> bar(new int); std::cout << "foo unique?\n" << std::boolalpha; std::cout << "1: " << foo.unique() << '\n'; // false (empty) foo = bar; std::cout << "2: " << foo.unique() << '\n'; // false (shared with bar) bar = nullptr; std::cout << "3: " << foo.unique() << '\n'; // true return 0; }
(11)“bool”操作
函數聲明:explicit operator bool() const noexcept; 檢查是否為 null。 返回存儲的指針是否為空指針。 存儲的指針指向 shared_ptr 對象解除引用的對象,通常與其擁有的指針相同(銷毀時刪除的指針)。 如果 shared_ptr 對象是別名(即別名構造的對象及其副本),它們可能會有所不同。該函數返回的結果與 get()!=0 相同。 請注意,空的 shared_ptr(即此函數返回 false 的指針)不一定是空的 shared_ptr。 別名可能擁有某個指針但指向空,或者所有者組甚至可能擁有空指針(參見構造函數 4 和 5)。
用法舉例:
#include <iostream> #include <memory> int main() { std::shared_ptr<int> foo; std::shared_ptr<int> bar(new int(34)); if (foo) std::cout << "foo points to " << *foo << '\n'; else std::cout << "foo is null\n"; if (bar) std::cout << "bar points to " << *bar << '\n'; else std::cout << "bar is null\n"; return 0; }
(12)owner_before函數
函數聲明:
template <class U> bool owner_before (const shared_ptr<U>& x) const;
template <class U> bool owner_before (const weak_ptr<U>& x) const;
基於所有者的排序。根據嚴格的弱基於所有者的順序返回是否認為對象在 x 之前。與 operator< 重載不同,此排序考慮了 shared_ptr 的擁有指針,而不是存儲的指針,使得兩個 如果它們都共享所有權,或者它們都為空,即使它們存儲的指針值不同,這些對象中的一個被認為是等效的(即,無論操作數的順序如何,該函數都返回 false)。 如果 shared_ptr 對象是一個別名(別名構造的對象及其副本),則 shared_ptr 對象解引用)可能不是擁有的指針(即對象銷毀時刪除的指針)。該函數由 owner_less 調用以確定其結果。
用法舉例:
#include <iostream> #include <memory> int main() { int* p = new int(10); std::shared_ptr<int> a(new int(20)); std::shared_ptr<int> b(a, p); // alias constructor std::cout << "comparing a and b...\n" << std::boolalpha; std::cout << "value-based: " << (!(a < b) && !(b < a)) << '\n'; std::cout << "owner-based: " << (!a.owner_before(b) && !b.owner_before(a)) << '\n'; delete p; return 0; }