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 可能與兩個指標相關:
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;
}