Kapan std :: kelemahan_ptr berguna?

Jawaban:

231

Contoh yang baik adalah cache.

Untuk objek yang baru-baru ini diakses, Anda ingin menyimpannya dalam memori, jadi Anda memegang pointer kuat ke sana. Secara berkala, Anda memindai cache dan memutuskan objek mana yang belum diakses baru-baru ini. Anda tidak perlu menyimpannya di memori, jadi Anda menyingkirkan pointer kuat.

Tetapi bagaimana jika objek itu sedang digunakan dan beberapa kode lain memegang pointer kuat untuk itu? Jika cache menyingkirkan satu-satunya penunjuk ke objek, ia tidak akan pernah menemukannya lagi. Jadi, cache menyimpan pointer yang lemah ke objek-objek yang perlu ditemukannya jika mereka tetap tersimpan di memori.

Inilah yang dilakukan oleh pointer lemah - ini memungkinkan Anda untuk menemukan objek jika masih ada, tetapi tidak menyimpannya jika tidak ada yang membutuhkannya.

David Schwartz
sumber
8
Jadi std :: wake_ptr dapat menunjuk hanya di mana pointer lain menunjuk dan menunjuk ke nullptr ketika objek yang runcing dihapus / tidak diarahkan oleh pointer lain lagi?
27
@ RM: Pada dasarnya, ya. Ketika Anda memiliki pointer yang lemah, Anda dapat mencoba mempromosikannya ke pointer yang kuat. Jika objek itu masih ada (karena setidaknya ada satu pointer kuat ke sana masih ada) operasi itu berhasil dan memberi Anda pointer kuat untuk itu. Jika objek itu tidak ada (karena semua pointer kuat hilang), maka operasi itu gagal (dan biasanya Anda bereaksi dengan membuang pointer lemah).
David Schwartz
12
Sementara penunjuk yang kuat membuat objek tetap hidup, sebuah lemah_ptr dapat melihatnya ... tanpa mucking dengan waktu hidup objek.
The Vivandiere
3
Contoh lain, yang saya telah menggunakan beberapa kali setidaknya, adalah ketika menerapkan pengamat, kadang-kadang menjadi nyaman untuk memiliki subjek mempertahankan daftar petunjuk lemah dan melakukan pembersihan daftar sendiri. Menghemat sedikit upaya secara eksplisit menghapus pengamat ketika mereka dihapus, dan lebih penting lagi Anda tidak harus memiliki informasi tentang mata pelajaran yang tersedia ketika menghancurkan pengamat yang umumnya menyederhanakan banyak hal.
Jason C
3
Tunggu, apa yang salah dengan cache yang menyimpan shared_ptr dan hanya menghapusnya dari daftar ketika itu harus dihapus dari memori? Setiap pengguna akan memiliki shared_ptr semua sama dan sumber daya yang di-cache akan dihapus segera setelah semua pengguna selesai dengan itu.
rubenvb
299

std::weak_ptradalah cara yang sangat baik untuk memecahkan masalah penunjuk menggantung . Dengan hanya menggunakan pointer mentah, tidak mungkin untuk mengetahui apakah data yang direferensikan telah dialokasikan atau tidak. Sebagai gantinya, dengan membiarkan std::shared_ptrmengelola data, dan memasok std::weak_ptrke pengguna data, pengguna dapat memeriksa validitas data dengan menelepon expired()atau lock().

Anda tidak dapat melakukan ini std::shared_ptrsendirian, karena semua std::shared_ptrinstance berbagi kepemilikan data yang tidak dihapus sebelum semua instance std::shared_ptrdihapus. Berikut ini adalah contoh cara memeriksa penunjuk menggantung menggunakan lock():

#include <iostream>
#include <memory>

int main()
{
    // OLD, problem with dangling pointer
    // PROBLEM: ref will point to undefined data!

    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // NEW
    // SOLUTION: check expired() or lock() to determine if pointer is valid

    // empty definition
    std::shared_ptr<int> sptr;

    // takes ownership of pointer
    sptr.reset(new int);
    *sptr = 10;

    // get pointer to data without taking ownership
    std::weak_ptr<int> weak1 = sptr;

    // deletes managed object, acquires new pointer
    sptr.reset(new int);
    *sptr = 5;

    // get pointer to new data without taking ownership
    std::weak_ptr<int> weak2 = sptr;

    // weak1 is expired!
    if(auto tmp = weak1.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";

    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}
berjemur
sumber
1
Ok, seolah-olah jika Anda secara lokal menetapkan (memiliki) pointer ke null (menghapus memori), semua pointer (lemah) lainnya ke memori yang sama juga diatur ke null
Pat-Laugh
std::weak_ptr::lockmembuat baru std::shared_ptryang berbagi kepemilikan dari objek yang dikelola.
Sahib Yar
129

Jawaban lain, semoga lebih sederhana. (untuk sesama googler)

Misalkan Anda punya Teamdan Memberbenda.

Jelas itu hubungan: Teamobjek akan memiliki petunjuk untuk itu Members. Dan kemungkinan para anggota juga akan memiliki pointer belakang ke Teamobjek mereka .

Maka Anda memiliki siklus ketergantungan. Jika Anda menggunakan shared_ptr, objek tidak akan lagi secara otomatis dibebaskan ketika Anda meninggalkan referensi pada mereka, karena mereka referensi satu sama lain secara siklik. Ini adalah kebocoran memori.

Anda memecahkan ini dengan menggunakan weak_ptr. "Pemilik" biasanya menggunakan shared_ptrdan "dimiliki" menggunakan weak_ptruntuk induknya, dan mengubahnya sementara untuk shared_ptrsaat dibutuhkan akses ke induknya.

Simpan ptr lemah:

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

lalu gunakan saat dibutuhkan

shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
  // yes, it may fail if the parent was freed since we stored weak_ptr
} else {
  // do stuff
}
// tempParentSharedPtr is released when it goes out of scope
Offirmo
sumber
1
Bagaimana ini kebocoran memori? Jika tim dihancurkan maka akan merusak anggotanya, sehingga jumlah ref shared_ptr akan menjadi 0 dan juga dirusak?
paulm
4
Tim @paulm tidak akan menghancurkan anggota "nya". Intinya shared_ptradalah untuk berbagi kepemilikan, jadi tidak ada yang memiliki tanggung jawab khusus untuk membebaskan memori, itu dibebaskan secara otomatis ketika tidak lagi digunakan. Kecuali jika ada loop ... Anda mungkin memiliki beberapa tim berbagi pemain yang sama (tim yang lalu?). Jika objek tim "memiliki" anggota, maka tidak perlu menggunakan a shared_ptruntuk memulai.
Offirmo
1
Itu tidak akan menghancurkan mereka tetapi shared_ptr-nya akan keluar dari ruang lingkup dengan itu, mengurangi use_count, sehingga pada titik ini use_count adalah 0 dan sehingga shared_ptr akan menghapus apa yang menunjuk ke arahnya?
paulm
2
@ paulm Anda benar. Tetapi karena, dalam contoh ini, tim juga shared_ptrdirujuk oleh "anggota tim", kapan akan dihancurkan? Apa yang Anda gambarkan adalah kasus di mana tidak ada loop.
Offirmo
14
Tidak terlalu buruk, saya pikir. Jika seorang anggota dapat menjadi bagian dari banyak tim, menggunakan referensi tidak akan berhasil.
Mazyod
22

Inilah salah satu contoh, yang diberikan kepada saya oleh @jleahy: Misalkan Anda memiliki koleksi tugas, dijalankan secara tidak sinkron, dan dikelola oleh std::shared_ptr<Task>. Anda mungkin ingin melakukan sesuatu dengan tugas-tugas itu secara berkala, sehingga acara pengatur waktu dapat melintasi std::vector<std::weak_ptr<Task>>dan memberikan tugas-tugas itu sesuatu untuk dilakukan. Namun, secara bersamaan suatu tugas mungkin secara bersamaan memutuskan bahwa itu tidak lagi diperlukan dan mati. Timer sehingga dapat memeriksa apakah tugas itu masih hidup dengan membuat pointer bersama dari pointer lemah dan menggunakan pointer bersama, asalkan bukan nol.

Kerrek SB
sumber
4
: Kedengarannya seperti contoh yang baik tetapi bisakah Anda menjelaskan lebih banyak contoh? Saya berpikir ketika tugas selesai, itu harus sudah dihapus dari std :: vector <std :: lemah_ptr <Task>> tanpa pemeriksaan berkala. Jadi tidak yakin apakah std :: vector <std :: weak_ptr <>> sangat membantu di sini.
Gob00st
Komentar serupa dengan antrian: misalkan Anda memiliki objek dan antri untuk beberapa sumber, objek dapat dihapus saat menunggu. Jadi, jika Anda mengantri kelemahan_ptrs Anda tidak perlu repot menghapus entri dari antrian di sana. Weak_ptrs akan tidak valid dan kemudian dibuang ketika di-encoutnered.
zzz777
1
@ zzz777: Logika yang membatalkan objek bahkan mungkin tidak menyadari keberadaan antrian atau vektor pengamat. Jadi pengamat melakukan loop terpisah di atas pointer yang lemah, bekerja pada yang masih hidup, dan mengeluarkan yang mati dari wadah ...
Kerrek SB
1
@KerekSB: ya dan jika ada antrian, Anda bahkan tidak perlu membuat loop terpisah - maka tersedia sumber daya, Anda membuang lemah_ptrs kadaluarsa (jika ada) sampai Anda mendapatkan yang valid (jika ada).
zzz777
Anda juga bisa meminta utas menghapus diri dari koleksi, tetapi itu akan membuat ketergantungan dan membutuhkan penguncian.
curiousguy
16

Mereka berguna dengan Boost.Asio ketika Anda tidak dijamin bahwa objek target masih ada ketika penangan asinkron dipanggil. Caranya adalah dengan mengikat sebuah weak_ptrke objek handler asynchonous, menggunakan std::bindatau lambda menangkap.

void MyClass::startTimer()
{
    std::weak_ptr<MyClass> weak = shared_from_this();
    timer_.async_wait( [weak](const boost::system::error_code& ec)
    {
        auto self = weak.lock();
        if (self)
        {
            self->handleTimeout();
        }
        else
        {
            std::cout << "Target object no longer exists!\n";
        }
    } );
}

Ini adalah varian dari self = shared_from_this()idiom yang sering terlihat dalam Boost. Contoh lain, di mana penangan asinkron yang tertunda tidak akan memperpanjang masa pakai objek target, namun tetap aman jika objek target dihapus.

Emile Cormier
sumber
Mengapa butuh waktu lama untuk menemukan jawaban ini ... PS Anda tidak menggunakan tangkapan Andathis
Orwellophile
@Orwellophile diperbaiki. Kekuatan kebiasaan ketika menggunakan self = shared_from_this()idiom ketika pawang memanggil metode dalam kelas yang sama.
Emile Cormier
16

shared_ptr : memegang objek nyata.

kelemahan_ptr : digunakan lockuntuk terhubung ke pemilik asli atau mengembalikan NULL shared_ptrjika tidak.

ptr lemah

Secara kasar, weak_ptrperannya mirip dengan peran agen perumahan . Tanpa agen, untuk mendapatkan rumah sewaan kita mungkin harus memeriksa rumah acak di kota. Agen memastikan bahwa kami mengunjungi hanya rumah-rumah yang masih dapat diakses dan tersedia untuk disewa.

Saurav Sahu
sumber
14

weak_ptrjuga baik untuk memeriksa penghapusan objek yang benar - terutama dalam tes unit. Kasus penggunaan umum mungkin terlihat seperti ini:

std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());
Kue kering
sumber
13

Ketika menggunakan pointer, penting untuk memahami berbagai jenis pointer yang tersedia dan kapan menggunakan masing-masing pointer. Ada empat jenis pointer dalam dua kategori sebagai berikut:

  • Pointer mentah:
    • Pointer Baku [yaitu SomeClass* ptrToSomeClass = new SomeClass();]
  • Pointer pintar:
    • Pointer Unik [yaitu
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Pointer Bersama [yaitu
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Pointer Lemah [yaitu
      std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
      ]

Pointer mentah (kadang-kadang disebut sebagai "legacy pointer", atau "pointer C") memberikan perilaku pointer 'telanjang-tulang' dan merupakan sumber bug dan kebocoran memori yang umum. Pointer mentah tidak menyediakan sarana untuk melacak kepemilikan sumber daya dan pengembang harus memanggil 'delete' secara manual untuk memastikan mereka tidak membuat kebocoran memori. Ini menjadi sulit jika sumber daya dibagikan karena dapat menjadi tantangan untuk mengetahui apakah ada benda yang masih menunjuk ke sumber daya. Karena alasan ini, pointer mentah umumnya harus dihindari dan hanya digunakan di bagian kritis-kinerja dari kode dengan cakupan terbatas.

Pointer unik adalah pointer cerdas dasar yang 'memiliki' pointer mentah yang mendasari ke sumber daya dan bertanggung jawab untuk memanggil hapus dan membebaskan memori yang dialokasikan setelah objek yang 'memiliki' pointer unik keluar dari ruang lingkup. Nama 'unik' merujuk pada fakta bahwa hanya satu objek yang dapat 'memiliki' pointer unik pada titik waktu tertentu. Kepemilikan dapat ditransfer ke objek lain melalui perintah move, tetapi pointer unik tidak pernah dapat disalin atau dibagikan. Untuk alasan ini, pointer unik adalah alternatif yang baik untuk pointer mentah jika hanya ada satu objek yang membutuhkan pointer pada waktu tertentu, dan ini mengurangi pengembang dari kebutuhan untuk membebaskan memori pada akhir siklus hidup objek yang dimiliki.

Pointer bersama adalah tipe lain dari pointer cerdas yang mirip dengan pointer unik, tetapi memungkinkan banyak objek memiliki kepemilikan atas pointer bersama. Seperti pointer unik, pointer bersama bertanggung jawab untuk membebaskan memori yang dialokasikan setelah semua objek selesai menunjuk ke sumber daya. Ini menyelesaikan ini dengan teknik yang disebut penghitungan referensi. Setiap kali objek baru mengambil kepemilikan dari pointer bersama, jumlah referensi bertambah satu. Demikian pula, ketika suatu objek keluar dari ruang lingkup atau berhenti menunjuk ke sumber daya, jumlah referensi dikurangi oleh satu. Ketika jumlah referensi mencapai nol, memori yang dialokasikan akan dibebaskan. Untuk alasan ini, pointer bersama adalah jenis pointer pintar yang sangat kuat yang harus digunakan kapan saja beberapa objek perlu menunjuk ke sumber daya yang sama.

Akhirnya, pointer lemah adalah tipe lain dari pointer cerdas yang, alih-alih menunjuk ke sumber daya secara langsung, mereka menunjuk ke pointer lain (lemah atau dibagi). Pointer yang lemah tidak dapat mengakses objek secara langsung, tetapi mereka dapat mengetahui apakah objek tersebut masih ada atau jika telah kedaluwarsa. Pointer yang lemah dapat sementara dikonversi menjadi pointer bersama untuk mengakses objek runcing-ke (asalkan masih ada). Untuk menggambarkan, pertimbangkan contoh berikut:

  • Anda sibuk dan memiliki pertemuan yang tumpang tindih: Rapat A dan Rapat B
  • Anda memutuskan untuk pergi ke Rapat A dan rekan kerja Anda pergi ke Rapat B
  • Anda memberi tahu rekan kerja Anda bahwa jika Rapat B masih berjalan setelah Rapat A berakhir, Anda akan bergabung
  • Dua skenario berikut dapat dimainkan:
    • Rapat A berakhir dan Rapat B masih berjalan, jadi Anda bergabung
    • Rapat A berakhir dan Rapat B juga berakhir, jadi Anda tidak bisa bergabung

Dalam contoh, Anda memiliki pointer lemah ke Rapat B. Anda bukan "pemilik" di Rapat B sehingga dapat berakhir tanpa Anda, dan Anda tidak tahu apakah itu berakhir atau tidak kecuali Anda memeriksa. Jika belum berakhir, Anda dapat bergabung dan berpartisipasi, jika tidak, Anda tidak bisa. Ini berbeda dari memiliki penunjuk bersama untuk Rapat B karena Anda kemudian akan menjadi "pemilik" di Rapat A dan Rapat B (berpartisipasi di keduanya pada saat yang sama).

Contoh tersebut menggambarkan bagaimana pointer lemah bekerja dan berguna ketika suatu objek perlu menjadi pengamat luar , tetapi tidak ingin tanggung jawab berbagi kepemilikan. Ini sangat berguna dalam skenario bahwa dua objek harus saling menunjuk (alias referensi melingkar). Dengan pointer bersama, tidak ada objek yang dapat dilepaskan karena mereka masih 'sangat' ditunjukkan oleh objek lain. Ketika salah satu pointer adalah pointer lemah, objek yang memegang pointer lemah masih dapat mengakses objek lain saat dibutuhkan, asalkan masih ada.

Jeremy
sumber
6

Terlepas dari kasus penggunaan yang valid yang telah disebutkan lainnya std::weak_ptradalah alat yang luar biasa dalam lingkungan multithreaded, karena

  • Itu tidak memiliki objek dan jadi tidak dapat menghalangi penghapusan di utas yang berbeda
  • std::shared_ptrdalam hubungannya dengan std::weak_ptraman terhadap pointer menggantung - berlawanan dengan std::unique_ptrdalam hubungannya dengan pointer mentah
  • std::weak_ptr::lock()adalah operasi atom (lihat juga Tentang keamanan utas dari lemah_ptr )

Pertimbangkan tugas untuk memuat semua gambar direktori (~ 10.000) secara bersamaan ke dalam memori (mis. Sebagai cache thumbnail). Jelas cara terbaik untuk melakukan ini adalah thread kontrol, yang menangani dan mengelola gambar, dan beberapa thread pekerja, yang memuat gambar. Sekarang ini adalah tugas yang mudah. Berikut ini adalah implementasi yang sangat disederhanakan ( join()dll dihilangkan, utas harus ditangani secara berbeda dalam implementasi nyata dll)

// a simplified class to hold the thumbnail and data
struct ImageData {
  std::string path;
  std::unique_ptr<YourFavoriteImageLibData> image;
};

// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageData : imagesToLoad )
     imageData->image = YourFavoriteImageLib::load( imageData->path );
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas = 
        splitImageDatas( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

Tetapi itu menjadi jauh lebih rumit, jika Anda ingin mengganggu pemuatan gambar, misalnya karena pengguna telah memilih direktori yang berbeda. Atau bahkan jika Anda ingin menghancurkan manajer.

Anda perlu komunikasi utas dan harus menghentikan semua utas loader, sebelum Anda dapat mengubah m_imageDatasbidang Anda . Kalau tidak, loader akan melanjutkan memuat sampai semua gambar selesai - bahkan jika mereka sudah usang. Dalam contoh sederhana, itu tidak akan terlalu sulit, tetapi dalam lingkungan nyata hal-hal bisa menjadi jauh lebih rumit.

Utas mungkin akan menjadi bagian dari rangkaian utas yang digunakan oleh beberapa manajer, di mana sebagian dihentikan, dan sebagian tidak dll. Parameter sederhana imagesToLoadakan berupa antrian terkunci, di mana manajer tersebut mendorong permintaan gambar mereka dari utas kontrol yang berbeda dengan pembaca membuka permintaan - dalam urutan acak - di ujung lainnya. Sehingga komunikasi menjadi sulit, lambat dan rawan kesalahan. Cara yang sangat elegan untuk menghindari komunikasi tambahan dalam kasus tersebut adalah dengan menggunakannya std::shared_ptrbersama std::weak_ptr.

// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageDataWeak : imagesToLoad ) {
     std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
     if( !imageData )
        continue;
     imageData->image = YourFavoriteImageLib::load( imageData->path );
   }
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas = 
        splitImageDatasToWeak( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

Implementasi ini hampir semudah yang pertama, tidak memerlukan komunikasi utas tambahan, dan dapat menjadi bagian dari kumpulan utas / antrian dalam implementasi nyata. Karena gambar yang kedaluwarsa dilewati, dan gambar yang tidak kedaluwarsa diproses, utas tidak akan pernah harus dihentikan selama operasi normal. Anda selalu dapat dengan aman mengubah jalur atau menghancurkan pengelola Anda, karena pembaca dapat memeriksa, jika penunjuk kepemilikan tidak kedaluwarsa.

pengguna2328447
sumber
2

http://en.cppreference.com/w/cpp/memory/weak_ptr std :: lemah_ptr adalah penunjuk pintar yang memegang referensi yang tidak memiliki ("lemah") ke objek yang dikelola oleh std :: shared_ptr. Itu harus dikonversi ke std :: shared_ptr untuk mengakses objek yang dirujuk.

std :: lemah_ptr memodelkan kepemilikan sementara: ketika suatu objek hanya perlu diakses jika ada, dan itu dapat dihapus kapan saja oleh orang lain, std :: lemah_ptr digunakan untuk melacak objek, dan itu dikonversi ke std: : shared_ptr untuk mengambil alih kepemilikan sementara. Jika std :: shared_ptr asli dihancurkan saat ini, masa hidup objek akan diperpanjang hingga std sementara: shared_ptr dihancurkan juga.

Selain itu, std :: lemah_ptr digunakan untuk memecah referensi melingkar std :: shared_ptr.

MYLOGOS
sumber
" untuk memecah referensi melingkar " bagaimana?
curiousguy
2

Ada kekurangan dari pointer bersama: shared_pointer tidak bisa menangani ketergantungan siklus orangtua-anak. Berarti jika kelas induk menggunakan objek kelas anak menggunakan pointer bersama, dalam file yang sama jika kelas anak menggunakan objek dari kelas induk. Pointer bersama akan gagal menghancurkan semua objek, bahkan pointer bersama sama sekali tidak memanggil destruktor dalam skenario ketergantungan siklus. pada dasarnya pointer bersama tidak mendukung mekanisme jumlah referensi.

Kekurangan ini bisa kita atasi menggunakan kelemahan_pointer.

ashutosh
sumber
Bagaimana referensi yang lemah dapat menangani ketergantungan sirkuler?
curiousguy
1
@curiousguy, seorang anak menggunakan referensi yang lemah ke orang tua, maka orang tua dapat dibatalkan alokasi ketika tidak ada referensi bersama (kuat) yang menunjuk ke sana. Jadi ketika mengakses orang tua melalui anak, referensi yang lemah harus diuji untuk melihat apakah orang tua masih tersedia. Atau untuk menghindari kondisi tambahan itu, mekanisme pelacakan referensi melingkar (baik mark-sweep atau probing pada penurunan jumlah, yang keduanya memiliki kinerja asimptotik yang buruk) dapat mematahkan referensi bersama yang melingkar ketika satu-satunya referensi yang dibagikan kepada orang tua dan anak dari masing-masing. lain.
Shelby Moore III
@ShelbyMooreIII " harus diuji untuk melihat apakah induknya masih tersedia " ya, dan Anda harus dapat bereaksi dengan benar pada kasing yang tidak tersedia! Yang tidak terjadi dengan ref nyata (yaitu kuat). Yang berarti ref lemah bukan setetes pengganti: itu membutuhkan perubahan dalam logika.
curiousguy
2
@curiousguy Anda tidak bertanya, "Bagaimana mungkin weak_ptrkesepakatan dengan ketergantungan sirkuler tanpa perubahan logika program sebagai pengganti drop-in shared_ptr?" :-)
Shelby Moore III
2

Ketika kami tidak ingin memiliki objek:

Ex:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

Di kelas di atas, wPtr1 tidak memiliki sumber daya yang ditunjukkan oleh wPtr1. Jika sumber daya terhapus maka wPtr1 kedaluwarsa.

Untuk menghindari ketergantungan sirkular:

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

Sekarang jika kita membuat shared_ptr dari kelas B dan A, use_count dari kedua pointer adalah dua.

Ketika shared_ptr keluar lingkup od hitungan masih tetap 1 dan karenanya objek A dan B tidak akan dihapus.

class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

keluaran:

A()
B()

Seperti yang dapat kita lihat dari output bahwa pointer A dan B tidak pernah dihapus dan karenanya kehabisan memori.

Untuk menghindari masalah seperti itu, gunakan lemah_ptr di kelas A alih-alih shared_ptr yang lebih masuk akal.

Swapnil
sumber
2

Saya melihat std::weak_ptr<T>sebagai pegangan untuk std::shared_ptr<T>: Ini memungkinkan saya untuk mendapatkan std::shared_ptr<T>jika masih ada, tetapi tidak akan memperpanjang masa pakainya. Ada beberapa skenario saat sudut pandang seperti itu berguna:

// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;

// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.

struct Widget {
    std::weak_ptr< Texture > texture_handle;
    void render() {
        if (auto texture = texture_handle.get(); texture) {
            // do stuff with texture. Warning: `texture`
            // is now extending the lifetime because it
            // is a std::shared_ptr< Texture >.
        } else {
            // gracefully degrade; there's no texture.
        }
    }
};

Skenario penting lainnya adalah memutus siklus dalam struktur data.

// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > next;
    std::shared_ptr< Node > prev;
};

// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::shared_ptr< Node > next;
    std::weak_ptr< Node > prev;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::weak_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

Herb Sutter memiliki ceramah luar biasa yang menjelaskan penggunaan fitur bahasa terbaik (dalam hal ini smart pointer) untuk memastikan Kebocoran Kebocoran secara Default (artinya: semuanya mengklik di tempat dengan konstruksi; Anda tidak dapat mengacaukannya). Ini harus diwaspadai.

Escualo
sumber