Cara: Membuat dan Menggunakan instans shared_ptr

Jenisnya shared_ptr adalah penunjuk cerdas di pustaka standar C++ yang dirancang untuk skenario di mana lebih dari satu pemilik perlu mengelola masa pakai objek. Setelah menginisialisasi, shared_ptr Anda dapat menyalinnya, meneruskannya berdasarkan nilai dalam argumen fungsi, dan menetapkannya ke instans lain shared_ptr . Semua instans menunjuk ke objek yang sama, dan berbagi akses ke satu "blok kontrol" yang menambah dan mengurangi jumlah referensi setiap kali baru shared_ptr ditambahkan, keluar dari cakupan, atau diatur ulang. Ketika jumlah referensi mencapai nol, blok kontrol menghapus sumber daya memori dan itu sendiri.

Ilustrasi berikut menunjukkan beberapa shared_ptr instans yang menunjuk ke satu lokasi memori.

Diagram memperlihatkan dua instans shared_ptr menunjuk ke satu lokasi memori.

Diagram pertama menunjukkan pointer bersama, P1, yang menunjuk ke instans MyClass serta blok kontrol dengan jumlah ref = 1. Diagram kedua menunjukkan penambahan pointer bersama lainnya, P2, yang juga menunjuk ke instans MyClass dan ke blok kontrol bersama, yang sekarang memiliki jumlah ref 2.

Contoh penyiapan

Contoh berikut mengasumsikan bahwa Anda telah menyertakan header yang diperlukan dan mendeklarasikan jenis yang diperlukan, seperti yang ditunjukkan di sini:

// 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
}

Contoh 1

Jika memungkinkan, gunakan fungsi make_shared untuk membuat shared_ptr saat sumber daya memori dibuat untuk pertama kalinya. make_shared aman pengecualian. Ini menggunakan panggilan yang sama untuk mengalokasikan memori untuk blok kontrol dan sumber daya, yang mengurangi overhead konstruksi. Jika Anda tidak menggunakan make_shared, maka Anda harus menggunakan ekspresi eksplisit new untuk membuat objek sebelum meneruskannya ke shared_ptr konstruktor. Contoh berikut menunjukkan berbagai cara untuk mendeklarasikan dan menginisialisasi shared_ptr bersama dengan objek baru.


// 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");

Contoh 2

Contoh berikut menunjukkan cara mendeklarasikan dan menginisialisasi shared_ptr instans yang mengambil kepemilikan bersama atas objek yang dialokasikan oleh objek lain shared_ptr. Asumsikan bahwa sp2 adalah inisialisasi 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);

Contoh 3

shared_ptr juga berguna dalam kontainer Pustaka Standar C++ saat Anda menggunakan algoritma yang menyalin elemen. Anda dapat membungkus elemen dalam shared_ptr, dan kemudian menyalinnya ke kontainer lain dengan pemahaman bahwa memori yang mendasar valid selama Anda membutuhkannya, dan tidak lagi. Contoh berikut menunjukkan cara menggunakan remove_copy_if algoritma pada shared_ptr instans di vektor.

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;
}

Contoh 4

Anda dapat menggunakan dynamic_pointer_cast, static_pointer_cast, dan const_pointer_cast untuk melemparkan shared_ptr. Fungsi-fungsi ini menyerupai dynamic_castoperator , static_cast, dan const_cast . Contoh berikut menunjukkan cara menguji jenis turunan dari setiap elemen dalam vektor shared_ptr kelas dasar, lalu menyalin elemen dan menampilkan informasi tentang elemen tersebut.

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;
}

Contoh 5

Anda dapat meneruskan shared_ptr ke fungsi lain dengan cara berikut:

  • Teruskan shared_ptr menurut nilai. Ini memanggil konstruktor salinan, menaikkan jumlah referensi, dan menjadikan penerima panggilan sebagai pemilik. Ada sejumlah kecil overhead dalam operasi ini, yang mungkin signifikan tergantung pada berapa banyak shared_ptr objek yang Anda lewati. Gunakan opsi ini ketika kontrak kode tersirat atau eksplisit antara penelepon dan penerima panggilan mengharuskan penerima panggilan menjadi pemilik.

  • shared_ptr Berikan referensi berdasarkan atau referensi const. Dalam hal ini, jumlah referensi tidak bertambah, dan penerima panggilan dapat mengakses penunjuk selama pemanggil tidak keluar dari cakupan. Atau, penerima panggilan dapat memutuskan untuk membuat shared_ptr berdasarkan referensi, dan menjadi pemilik bersama. Gunakan opsi ini ketika penelepon tidak memiliki pengetahuan tentang penerima panggilan, atau ketika Anda harus meneruskan shared_ptr dan ingin menghindari operasi penyalinan karena alasan performa.

  • Teruskan penunjuk yang mendasar atau referensi ke objek yang mendasar. Ini memungkinkan penerima panggilan untuk menggunakan objek, tetapi tidak memungkinkannya untuk berbagi kepemilikan atau memperpanjang masa pakai. Jika penerima panggilan membuat shared_ptr dari pointer mentah, yang baru shared_ptr independen dari aslinya, dan tidak mengontrol sumber daya yang mendasar. Gunakan opsi ini ketika kontrak antara penelepon dan penerima panggilan dengan jelas menentukan bahwa penelepon mempertahankan kepemilikan shared_ptr seumur hidup.

  • Saat Anda memutuskan cara meneruskan shared_ptr, tentukan apakah penerima panggilan harus berbagi kepemilikan sumber daya yang mendasar. "Pemilik" adalah objek atau fungsi yang dapat menjaga sumber daya yang mendasar tetap hidup selama dibutuhkan. Jika penelepon harus menjamin bahwa penerima panggilan dapat memperpanjang masa pakai pointer di luar masa pakainya (fungsi), gunakan opsi pertama. Jika Anda tidak peduli apakah penerima panggilan memperpanjang masa pakai, maka lewati dengan referensi dan biarkan penerima panggilan menyalinnya atau tidak.

  • Jika Anda harus memberikan akses fungsi pembantu ke penunjuk yang mendasar, dan Anda tahu bahwa fungsi pembantu menggunakan penunjuk dan mengembalikan sebelum fungsi panggilan kembali, maka fungsi tersebut tidak perlu berbagi kepemilikan pointer yang mendasar. Ini hanya harus mengakses penunjuk dalam masa pakai pemanggil shared_ptr. Dalam hal ini, aman untuk melewati shared_ptr referensi, atau meneruskan pointer mentah atau referensi ke objek yang mendasar. Melewati cara ini memberikan manfaat performa kecil, dan juga dapat membantu Anda mengekspresikan niat pemrograman Anda.

  • Terkadang, misalnya dalam std::vector<shared_ptr<T>>, Anda mungkin harus meneruskan masing-masing shared_ptr ke isi ekspresi lambda atau objek fungsi bernama. Jika lambda atau fungsi tidak menyimpan penunjuk, teruskan shared_ptr dengan referensi untuk menghindari pemanggilan konstruktor salinan untuk setiap elemen.

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));
}

Contoh 6

Contoh berikut menunjukkan cara shared_ptr membebani berbagai operator perbandingan untuk mengaktifkan perbandingan pointer pada memori yang dimiliki oleh shared_ptr instans.


// 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; 

Lihat juga

Penunjuk Cerdas (Modern C++)