共享所有權(stdshared ptr)

類别範本 std::shared_ptr 定義了一個共享指標,該指標能夠與其他共享指標共享物件的所有權。這與代表獨家所有權的 std::unique_ptr 形成對比。

共享行為通過稱為引用計數的技術實現,其中指向物件的共享指標的數量與其一起儲存。當此計數達到零時,無論是通過銷燬還是重新分配最後一個 std::shared_ptr 例項,物件都會自動銷燬。

// Creation: 'firstShared' is a shared pointer for a new instance of 'Foo'
std::shared_ptr<Foo> firstShared = std::make_shared<Foo>(/*args*/);

要建立共享同一物件的多個智慧指標,我們需要建立另一個使第一個共享指標別名的 shared_ptr。這有兩種方法:

std::shared_ptr<Foo> secondShared(firstShared);  // 1st way: Copy constructing
std::shared_ptr<Foo> secondShared;
secondShared = firstShared;                      // 2nd way: Assigning

上述兩種方法都使得 secondShared 成為共享指標,與 firstShared 共享我們的 Foo 例項的所有權。

智慧指標就像原始指標一樣工作。這意味著,你可以使用*取消引用它們。常規的 -> 運算子也可以執行:

secondShared->test(); // Calls Foo::test()

最後,當最後一個別名 shared_ptr 超出範圍時,我們的 Foo 例項的解構函式被呼叫。

警告: 當需要分配共享所有權語義的額外資料時,構造 shared_ptr 可能會丟擲 bad_alloc 異常。如果建構函式傳遞了常規指標,則它假定擁有指向的物件,並在丟擲異常時呼叫刪除器。這意味著如果 shared_ptr<T> 的分配失敗,shared_ptr<T>(new T(args)) 將不會洩漏 T 物件。但是,建議使用 make_shared<T>(args)allocate_shared<T>(alloc, args),這使得實現能夠優化記憶體分配。

使用 shared_ptr 分配陣列([])

Version < C++ 17

不幸的是,沒有直接的方法來使用 make_shared<> 分配陣列。

可以使用 newstd::default_deleteshared_ptr<> 建立陣列。

例如,要分配 10 個整數的陣列,我們可以將程式碼編寫為

shared_ptr<int> sh(new int[10], std::default_delete<int[]>());

此處必須指定 std::default_delete 以確保使用 delete[] 正確清除分配的記憶體。

如果我們在編譯時知道大小,我們可以這樣做:

template<class Arr>
struct shared_array_maker {};
template<class T, std::size_t N>
struct shared_array_maker<T[N]> {
  std::shared_ptr<T> operator()const{
    auto r = std::make_shared<std::array<T,N>>();
    if (!r) return {};
    return {r.data(), r};
  }
};
template<class Arr>
auto make_shared_array()
-> decltype( shared_array_maker<Arr>{}() )
{ return shared_array_maker<Arr>{}(); }

然後 make_shared_array<int[10]> 返回一個指向 10 個整數的 shared_ptr<int>,所有預設構造。

Version >= C++ 17

使用 C++ 17,shared_ptr 獲得了對陣列型別的特殊支援 。不再需要顯式指定陣列刪除器,並且可以使用 [] 陣列索引運算子取消引用共享指標:

std::shared_ptr<int[]> sh(new int[10]);
sh[0] = 42;

共享指標可以指向它擁有的物件的子物件:

struct Foo { int x; };
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::shared_ptr<int> p2(p1, &p1->x);

p2p1 都擁有 Foo 型別的物件,但是 p2 指向它的 int 成員 x。這意味著如果 p1 超出範圍或被重新分配,底層的 Foo 物件仍將存活,確保 p2 不會懸掛。

重要提示: shared_ptr 只知道自己和使用別名建構函式建立的所有其他 shared_ptr。它不知道任何其他指標,包括通過引用同一 Foo 例項建立的所有其他 shared_ptr

Foo *foo = new Foo;
std::shared_ptr<Foo> shared1(foo);
std::shared_ptr<Foo> shared2(foo); // don't do this

shared1.reset(); // this will delete foo, since shared1
                 // was the only shared_ptr that owned it

shared2->test(); // UNDEFINED BEHAVIOR: shared2's foo has been
                 // deleted already!!

所有權轉讓 shared_ptr

預設情況下,shared_ptr 會增加引用計數,並且不會轉移所有權。但是,可以使用 std::move 轉移所有權:

shared_ptr<int> up = make_shared<int>();
// Transferring the ownership
shared_ptr<int> up2 = move(up);
// At this point, the reference count of up = 0 and the
// ownership of the pointer is solely with up2 with reference count = 1