Jawaban:
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.
std::weak_ptr
adalah 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_ptr
mengelola data, dan memasok std::weak_ptr
ke pengguna data, pengguna dapat memeriksa validitas data dengan menelepon expired()
atau lock()
.
Anda tidak dapat melakukan ini std::shared_ptr
sendirian, karena semua std::shared_ptr
instance berbagi kepemilikan data yang tidak dihapus sebelum semua instance std::shared_ptr
dihapus. 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";
}
std::weak_ptr::lock
membuat baru std::shared_ptr
yang berbagi kepemilikan dari objek yang dikelola.
Jawaban lain, semoga lebih sederhana. (untuk sesama googler)
Misalkan Anda punya Team
dan Member
benda.
Jelas itu hubungan: Team
objek akan memiliki petunjuk untuk itu Members
. Dan kemungkinan para anggota juga akan memiliki pointer belakang ke Team
objek 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_ptr
dan "dimiliki" menggunakan weak_ptr
untuk induknya, dan mengubahnya sementara untuk shared_ptr
saat 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
shared_ptr
adalah 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_ptr
untuk memulai.
shared_ptr
dirujuk oleh "anggota tim", kapan akan dihancurkan? Apa yang Anda gambarkan adalah kasus di mana tidak ada loop.
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.
Mereka berguna dengan Boost.Asio ketika Anda tidak dijamin bahwa objek target masih ada ketika penangan asinkron dipanggil. Caranya adalah dengan mengikat sebuah weak_ptr
ke objek handler asynchonous, menggunakan std::bind
atau 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.
this
self = shared_from_this()
idiom ketika pawang memanggil metode dalam kelas yang sama.
shared_ptr : memegang objek nyata.
kelemahan_ptr : digunakan lock
untuk terhubung ke pemilik asli atau mengembalikan NULL shared_ptr
jika tidak.
Secara kasar, weak_ptr
perannya 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.
weak_ptr
juga 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());
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:
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
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:
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.
Terlepas dari kasus penggunaan yang valid yang telah disebutkan lainnya std::weak_ptr
adalah alat yang luar biasa dalam lingkungan multithreaded, karena
std::shared_ptr
dalam hubungannya dengan std::weak_ptr
aman terhadap pointer menggantung - berlawanan dengan std::unique_ptr
dalam hubungannya dengan pointer mentahstd::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_imageDatas
bidang 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 imagesToLoad
akan 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_ptr
bersama 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.
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.
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.
weak_ptr
kesepakatan dengan ketergantungan sirkuler tanpa perubahan logika program sebagai pengganti drop-in shared_ptr
?" :-)
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.
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.