Jawaban:
Ini memungkinkan Anda untuk mendapatkan shared_ptrcontoh yang valid untuk this, ketika semua yang Anda miliki adalah this. Tanpa itu, Anda akan memiliki cara untuk mendapatkan shared_ptruntuk this, kecuali jika Anda sudah memiliki satu sebagai anggota. Contoh ini dari dokumentasi boost untuk enable_shared_from_ini :
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_from_this();
}
}
int main()
{
shared_ptr<Y> p(new Y);
shared_ptr<Y> q = p->f();
assert(p == q);
assert(!(p < q || q < p)); // p and q must share ownership
}
Metode f()mengembalikan yang valid shared_ptr, meskipun tidak memiliki turunan anggota. Perhatikan bahwa Anda tidak bisa begitu saja melakukan ini:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_ptr<Y>(this);
}
}
Pointer bersama yang dikembalikan ini akan memiliki jumlah referensi yang berbeda dari yang "tepat", dan salah satu dari mereka akan kehilangan dan menahan referensi yang menggantung ketika objek dihapus.
enable_shared_from_thistelah menjadi bagian dari standar C ++ 11. Anda juga bisa mendapatkannya dari sana juga dari boost.
std::shared_ptrkonstruktor pada pointer mentah jika diwarisi dari std::enable_shared_from_this. Saya tidak tahu apakah semantik Boost diperbarui untuk mendukung ini.
std::shared_ptruntuk objek yang sudah dikelola oleh orang lain std::shared_ptrtidak akan berkonsultasi dengan referensi lemah yang disimpan secara internal dan dengan demikian akan menyebabkan perilaku yang tidak terdefinisi." ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
shared_ptr<Y> q = p?
std::make_shared<T>.
dari artikel Dr Dobbs tentang pointer lemah, saya pikir contoh ini lebih mudah dimengerti (sumber: http://drdobbs.com/cpp/184402026 ):
... kode seperti ini tidak akan berfungsi dengan benar:
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
Tak satu pun dari dua shared_ptrobjek tahu tentang yang lain, sehingga keduanya akan mencoba melepaskan sumber daya ketika mereka dihancurkan. Itu biasanya mengarah pada masalah.
Demikian pula, jika fungsi anggota memerlukan shared_ptrobjek yang memiliki objek yang dipanggil, ia tidak bisa hanya membuat objek dengan cepat:
struct S
{
shared_ptr<S> dangerous()
{
return shared_ptr<S>(this); // don't do this!
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->dangerous();
return 0;
}
Kode ini memiliki masalah yang sama dengan contoh sebelumnya, meskipun dalam bentuk yang lebih halus. Ketika dibangun, shared_ptobjek r sp1memiliki sumber daya yang baru dialokasikan. Kode di dalam fungsi anggota S::dangeroustidak tahu tentang shared_ptrobjek itu, jadi shared_ptrobjek yang dikembalikannya berbeda sp1. Menyalin shared_ptrobjek baru ke sp2tidak membantu; ketika sp2keluar dari ruang lingkup, itu akan melepaskan sumber daya, dan ketika sp1keluar dari ruang lingkup, itu akan melepaskan sumber daya lagi.
Cara untuk menghindari masalah ini adalah dengan menggunakan templat kelas enable_shared_from_this. Templat mengambil satu argumen jenis templat, yang merupakan nama kelas yang menentukan sumber daya yang dikelola. Kelas itu harus, pada gilirannya, diturunkan secara publik dari templat; seperti ini:
struct S : enable_shared_from_this<S>
{
shared_ptr<S> not_dangerous()
{
return shared_from_this();
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->not_dangerous();
return 0;
}
Ketika Anda melakukan ini, perlu diingat bahwa objek yang Anda panggil shared_from_thisharus dimiliki oleh suatu shared_ptrobjek. Ini tidak akan berfungsi:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
shared_ptr<S> sp1(new S);lebih disukai untuk digunakan shared_ptr<S> sp1 = make_shared<S>();, lihat misalnya stackoverflow.com/questions/18301511/…
shared_ptr<S> sp2 = p->not_dangerous();karena perangkap di sini adalah Anda harus membuat shared_ptr dengan cara yang normal sebelum Anda menelepon shared_from_this()pertama kali! Ini sangat mudah salah! Sebelum C ++ 17, ini adalah UB untuk memanggil shared_from_this()sebelum tepat satu shared_ptr telah dibuat dengan cara biasa: auto sptr = std::make_shared<S>();atau shared_ptr<S> sptr(new S());. Untungnya dari C ++ 17 dan seterusnya akan melempar.
S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();<- Diijinkan untuk memanggil shared_from_ini hanya pada objek yang dibagikan sebelumnya, yaitu pada objek yang dikelola oleh std :: shared_ptr <T>. Kalau tidak, perilaku tidak terdefinisi (sampai C ++ 17) std :: bad_weak_ptr dilemparkan (oleh constructor shared_ptr dari yang dibangun-lemah, lemah_ini) (sejak C ++ 17). . Jadi kenyataannya adalah bahwa itu harus dipanggil always_dangerous(), karena Anda perlu pengetahuan apakah sudah dibagikan atau belum.
Inilah penjelasan saya, dari perspektif mur dan baut (jawaban teratas tidak 'klik' dengan saya). * Perhatikan bahwa ini adalah hasil dari penyelidikan sumber untuk shared_ptr dan enable_shared_from_ini yang datang dengan Visual Studio 2012. Mungkin kompiler lain mengimplementasikan enable_shared_from_this berbeda ... *
enable_shared_from_this<T>menambahkan weak_ptr<T>contoh pribadi Tyang memegang ' satu hitungan referensi yang benar ' untuk contoh T.
Jadi, ketika Anda pertama kali membuat a shared_ptr<T>ke T * baru, itu melemah_ptr internal T * akan diinisialisasi dengan refcount dari 1. Yang baru shared_ptrpada dasarnya mendukung ini weak_ptr.
Tkemudian dapat, dalam metodenya, panggilan shared_from_thisuntuk mendapatkan sebuah instance dari shared_ptr<T>yang mendukung jumlah referensi yang disimpan secara internal yang sama . Dengan cara ini, Anda selalu memiliki satu tempat menyimpan T*penghitungan ulang alih-alih memiliki banyak shared_ptrcontoh yang tidak saling mengenal, dan masing-masing menganggap mereka shared_ptryang bertanggung jawab menghitung ulang Tdan menghapusnya saat ref -hitung mencapai nol.
So, when you first create...karena itu adalah persyaratan (seperti yang Anda katakan lemah_ptr tidak diinisialisasi sampai Anda meneruskan pointer objek ke dalam ctor shared_ptr!) Dan persyaratan ini adalah di mana segala sesuatu bisa menjadi sangat salah jika Anda kurang teliti. Jika Anda tidak membuat shared_ptr sebelum memanggil shared_from_thisAnda mendapatkan UB - demikian juga jika Anda membuat lebih dari satu shared_ptr, Anda juga mendapatkan UB. Anda harus entah bagaimana memastikan Anda membuat shared_ptr tepat sekali.
enable_shared_from_thisrapuh untuk memulai karena intinya adalah untuk bisa mendapatkan shared_ptr<T>dari T*, tetapi pada kenyataannya ketika Anda mendapatkan pointer T* t, umumnya tidak aman untuk menganggap apa pun tentang itu sudah dibagikan atau tidak, dan membuat tebakan yang salah adalah UB.
Perhatikan bahwa menggunakan boost :: intrusive_ptr tidak mengalami masalah ini. Ini sering merupakan cara yang lebih mudah untuk mengatasi masalah ini.
enable_shared_from_thismemungkinkan Anda untuk bekerja dengan API yang secara khusus menerima shared_ptr<>. Menurut pendapat saya, API seperti itu biasanya Doing It Wrong (karena lebih baik membiarkan sesuatu yang lebih tinggi di stack memiliki memori) tetapi jika Anda dipaksa untuk bekerja dengan API seperti itu, ini adalah pilihan yang baik.
Ini persis sama di c ++ 11 dan yang lebih baru: Ini untuk mengaktifkan kemampuan untuk kembali thissebagai pointer bersama karena thismemberi Anda pointer mentah.
dengan kata lain, ini memungkinkan Anda untuk mengubah kode seperti ini
class Node {
public:
Node* getParent const() {
if (m_parent) {
return m_parent;
} else {
return this;
}
}
private:
Node * m_parent = nullptr;
};
dalam hal ini:
class Node : std::enable_shared_from_this<Node> {
public:
std::shared_ptr<Node> getParent const() {
std::shared_ptr<Node> parent = m_parent.lock();
if (parent) {
return parent;
} else {
return shared_from_this();
}
}
private:
std::weak_ptr<Node> m_parent;
};
shared_ptr. Anda mungkin ingin mengubah antarmuka untuk memastikan itu terjadi.
std::shared_ptr<Node> getParent const(), saya biasanya akan mengeksposnya sebagai NodePtr getParent const()gantinya. Jika Anda benar-benar membutuhkan akses ke pointer mentah internal (contoh terbaik: berurusan dengan perpustakaan C), ada std::shared_ptr<T>::getuntuk itu, yang saya benci sebutkan karena saya sudah accessor pointer mentah ini digunakan terlalu banyak kali untuk alasan yang salah.
Cara lain adalah menambahkan weak_ptr<Y> m_stub anggota ke dalam class Y. Lalu menulis:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
Berguna saat Anda tidak dapat mengubah kelas tempat Anda berasal (misalnya memperluas perpustakaan orang lain). Jangan lupa untuk menginisialisasi anggota, misalnya dengan m_stub = shared_ptr<Y>(this), ini berlaku bahkan selama konstruktor.
Tidak apa-apa jika ada lebih banyak bertopik seperti ini dalam hierarki warisan, itu tidak akan mencegah penghancuran objek.
Sunting: Seperti yang ditunjukkan dengan benar oleh pengguna nobar, kode akan menghancurkan objek Y ketika tugas selesai dan variabel sementara dihancurkan. Karena itu jawaban saya salah.
shared_ptr<>yang tidak menghapus poinnya, ini berlebihan. Anda bisa mengatakan di return shared_ptr<Y>(this, no_op_deleter);mana no_op_deleterobjek fungsi unary mengambil Y*dan tidak melakukan apa pun.
m_stub = shared_ptr<Y>(this)akan membuat dan segera merusak shared_ptr sementara dari ini. Ketika pernyataan ini selesai, thisakan dihapus dan semua referensi selanjutnya akan menjuntai.
enable_shared_from_this, itu membuat itu weak_ptrsendiri (diisi oleh ctor), dikembalikan sebagai shared_ptrsaat Anda menelepon shared_from_this. Dengan kata lain, Anda menduplikasi apa yang enable_shared_from_thissudah disediakan.