如何:建立和使用 shared_ptr 執行個體

shared_ptr 類型是 C++ 標準程式庫中的一種智慧型指標,是為有一個以上的擁有者必須管理物件存留期的情節而設計。 在您初始化 shared_ptr 之後,您可以函式引數中的值予以複製、傳送以及指派至其他 shared_ptr 執行個體。 所有執行個體都會指向相同的物件,並共用對一個每當新的 shared_ptr 加入、超出範圍或重設時會遞增和遞減參考計數的「控制區塊」的存取。 當參考計數達到零時,控制區塊會刪除記憶體資源和自己本身。

下圖顯示幾個指向一個記憶體位置的 shared_ptr 執行個體。

此圖顯示指向一個記憶體位置的兩個 shared_ptr 執行個體。

第一個圖表顯示指向 MyClass 執行個體的共用指標 P1,以及具有 1 個參考計數的控制區塊。 第二個圖表顯示新增另一個共用指標 P2,該指標也指向 MyClass 執行個體和共用控制區塊,而共用控制區塊現在有 2 個參考計數。

範例設定

以下範例假設您已包含必要的標頭,並宣告了必要類型,如此處所示:

// shared_ptr-examples.cpp
// The following examples assume these declarations:
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct MediaAsset
{
    virtual ~MediaAsset() = default; // make it polymorphic
};

struct Song : public MediaAsset
{
    std::wstring artist;
    std::wstring title;
    Song(const std::wstring& artist_, const std::wstring& title_) :
        artist{ artist_ }, title{ title_ } {}
};

struct Photo : public MediaAsset
{
    std::wstring date;
    std::wstring location;
    std::wstring subject;
    Photo(
        const std::wstring& date_,
        const std::wstring& location_,
        const std::wstring& subject_) :
        date{ date_ }, location{ location_ }, subject{ subject_ } {}
};

using namespace std;

int main()
{
    // The examples go here, in order:
    // Example 1
    // Example 2
    // Example 3
    // Example 4
    // Example 6
}

範例 1

在任何可能的情況下,請在初次建立記憶體資源時使用 make_shared 函式來建立 shared_ptrmake_shared 是無例外狀況之虞。 它會使用相同的呼叫來配置控制區塊的記憶體及資源,減少建構的額外負荷。 若您不使用 make_shared,便必須使用明確的 new 運算式來建立物件,才能將物件傳遞至 shared_ptr 建構函式。 下列範例顯示各種宣告和初始化 shared_ptr 及新物件的方式。


// Use make_shared function when possible.
auto sp1 = make_shared<Song>(L"The Beatles", L"Im Happy Just to Dance With You");

// Ok, but slightly less efficient. 
// Note: Using new expression as constructor argument
// creates no named variable for other code to access.
shared_ptr<Song> sp2(new Song(L"Lady Gaga", L"Just Dance"));

// When initialization must be separate from declaration, e.g. class members, 
// initialize with nullptr to make your programming intent explicit.
shared_ptr<Song> sp5(nullptr);
//Equivalent to: shared_ptr<Song> sp5;
//...
sp5 = make_shared<Song>(L"Elton John", L"I'm Still Standing");

範例 2

下列範例顯示如何宣告和初始化 shared_ptr 執行個體,其具有被另一個 shared_ptr 所配置物件的共用擁有權。 假設 sp2 是已初始化的 shared_ptr

//Initialize with copy constructor. Increments ref count.
auto sp3(sp2);

//Initialize via assignment. Increments ref count.
auto sp4 = sp2;

//Initialize with nullptr. sp7 is empty.
shared_ptr<Song> sp7(nullptr);

// Initialize with another shared_ptr. sp1 and sp2
// swap pointers as well as ref counts.
sp1.swap(sp2);

範例 3

當您在 C++ 標準程式庫容器內使用會複製元素的演算法時,shared_ptr 也相當實用。 您可以包裝 shared_ptr 中的項目,然後將它複製到能夠辨識只有需要時才有效 (不再需要時則無效) 之基礎記憶體的其他容器中。 下列範例顯示如何在向量中的 remove_copy_if 執行個體上運用 shared_ptr 演算法。

vector<shared_ptr<Song>> v {
  make_shared<Song>(L"Bob Dylan", L"The Times They Are A Changing"),
  make_shared<Song>(L"Aretha Franklin", L"Bridge Over Troubled Water"),
  make_shared<Song>(L"Thalía", L"Entre El Mar y Una Estrella")
};

vector<shared_ptr<Song>> v2;
remove_copy_if(v.begin(), v.end(), back_inserter(v2), [] (shared_ptr<Song> s) 
{
    return s->artist.compare(L"Bob Dylan") == 0;
});

for (const auto& s : v2)
{
    wcout << s->artist << L":" << s->title << endl;
}

範例 4

您可以使用 dynamic_pointer_caststatic_pointer_castconst_pointer_cast 轉換 shared_ptr。 這些函式類似 dynamic_caststatic_castconst_cast 運算子。 下列範例顯示如何測試在基底類別的 shared_ptr 向量中每個項目的衍生類型,然後複製項目並顯示其相關資訊。

vector<shared_ptr<MediaAsset>> assets {
  make_shared<Song>(L"Himesh Reshammiya", L"Tera Surroor"),
  make_shared<Song>(L"Penaz Masani", L"Tu Dil De De"),
  make_shared<Photo>(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")
};

vector<shared_ptr<MediaAsset>> photos;

copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr<MediaAsset> p) -> bool
{
    // Use dynamic_pointer_cast to test whether
    // element is a shared_ptr<Photo>.
    shared_ptr<Photo> temp = dynamic_pointer_cast<Photo>(p);
    return temp.get() != nullptr;
});

for (const auto&  p : photos)
{
    // We know that the photos vector contains only 
    // shared_ptr<Photo> objects, so use static_cast.
    wcout << "Photo location: " << (static_pointer_cast<Photo>(p))->location << endl;
}

範例 5

您可以透過下列方式將 shared_ptr 傳遞至另一個函式:

  • 以傳值方式傳遞 shared_ptr。 這會叫用複製建構函式、遞增參考計數以及讓被呼叫端成為擁有者。 此作業中有少量的額外負荷,也可能視您傳遞的 shared_ptr 物件多寡而變多。 請在呼叫者和被呼叫者之間的隱含或明確程式碼合約要求被呼叫者成為擁有者時,使用此選項。

  • 以傳址或 const 的傳址方式傳遞 shared_ptr。 在這種情況下,參考計數不會遞增,並且只要呼叫者沒有離開範圍,被呼叫者都能存取該指標。 或者,被呼叫者可以決定根據參考建立一個 shared_ptr,並成為共用擁有者。 當呼叫端不了解被呼叫端或者您必須傳遞 shared_ptr 且要基於效能考量避免複製作業時,請使用這個選項。

  • 將基底指標或參考傳遞至基礎物件。 這可讓被呼叫者使用物件,但不會讓它共用擁有權或延長存留期。 若被呼叫者從原始指標建立 shared_ptr,則新的 shared_ptr 會獨立於原始的 shared_ptr,並且不會控制基礎資源。 當呼叫端和被呼叫端之間的協定明確指定呼叫端保留 shared_ptr 存留期的擁有權時,請使用這個選項。

  • 在您決定如何傳遞 shared_ptr 時,請判斷被呼叫者是否必須共用基礎資源的擁有權。 「擁有者」是一個只要需要時就讓基礎資源存活的物件或函式。 如果呼叫端必須確保被呼叫端可以延長指標的壽命為超過其 (函式的) 存留期,請使用第一個選項。 如果您不在乎被呼叫端是否延長存留期,則以傳址方式傳遞,並讓被呼叫端決定是否複製。

  • 若您必須讓 helper 函式存取基礎指標,並且您知道 helper 函式會在呼叫函式傳回之前使用指標並傳回,則該函式便不需要共用基礎指標的擁有權。 它只需要存取呼叫端之 shared_ptr 的存留期內的指標。 在這種情況下,以傳址方式傳遞 shared_ptr,或傳遞基礎物件的原始指標或參考是安全的。 以這種方式傳遞有一小小的效能優點,並且也可以協助您表達程式設計的意圖。

  • 在某些情況下,例如在 std::vector<shared_ptr<T>> 中,您可能必須將每個 shared_ptr 傳遞到 Lambda 運算式主體或具名函式物件。 若 lambda 或函式並未儲存指標,請以傳址方式傳遞 shared_ptr 來避免叫用每個元素的複製建構函式。

void use_shared_ptr_by_value(shared_ptr<int> sp);

void use_shared_ptr_by_reference(shared_ptr<int>& sp);
void use_shared_ptr_by_const_reference(const shared_ptr<int>& sp);

void use_raw_pointer(int* p);
void use_reference(int& r);

void test() {
    auto sp = make_shared<int>(5);

    // Pass the shared_ptr by value.
    // This invokes the copy constructor, increments the reference count, and makes the callee an owner.
    use_shared_ptr_by_value(sp);

    // Pass the shared_ptr by reference or const reference.
    // In this case, the reference count isn't incremented.
    use_shared_ptr_by_reference(sp);
    use_shared_ptr_by_const_reference(sp);

    // Pass the underlying pointer or a reference to the underlying object.
    use_raw_pointer(sp.get());
    use_reference(*sp);

    // Pass the shared_ptr by value.
    // This invokes the move constructor, which doesn't increment the reference count
    // but in fact transfers ownership to the callee.
    use_shared_ptr_by_value(move(sp));
}

範例 6

以下範例顯示 shared_ptr 如何多載各種比較運算子,以啟用 shared_ptr 執行個體所擁有之記憶體的指標比較。


// Initialize two separate raw pointers.
// Note that they contain the same values.
auto song1 = new Song(L"Village People", L"YMCA");
auto song2 = new Song(L"Village People", L"YMCA");

// Create two unrelated shared_ptrs.
shared_ptr<Song> p1(song1);    
shared_ptr<Song> p2(song2);

// Unrelated shared_ptrs are never equal.
wcout << "p1 < p2 = " << std::boolalpha << (p1 < p2) << endl;
wcout << "p1 == p2 = " << std::boolalpha <<(p1 == p2) << endl;

// Related shared_ptr instances are always equal.
shared_ptr<Song> p3(p2);
wcout << "p3 == p2 = " << std::boolalpha << (p3 == p2) << endl; 

另請參閱

智慧型指標 (現代 C++)