Implementasi C ++ Smart Pointer apa yang tersedia?

121

Perbandingan, Pro, Kontra, dan Kapan Menggunakan?

Ini adalah spin-off dari utas pengumpulan sampah di mana apa yang saya anggap sebagai jawaban sederhana menghasilkan banyak komentar tentang beberapa implementasi penunjuk cerdas tertentu sehingga sepertinya layak untuk memulai posting baru.

Pada akhirnya, pertanyaannya adalah apa saja berbagai implementasi smart pointer di C ++ di luar sana dan bagaimana cara membandingkannya? Hanya pro dan kontra sederhana atau pengecualian dan mendapatkan sesuatu yang menurut Anda seharusnya berhasil.

Saya telah memposting beberapa implementasi yang telah saya gunakan atau setidaknya abaikan dan pertimbangkan untuk digunakan sebagai jawaban di bawah ini dan pemahaman saya tentang perbedaan dan persamaan mereka yang mungkin tidak 100% akurat jadi silakan periksa fakta atau koreksi saya sesuai kebutuhan.

Tujuannya adalah untuk mempelajari tentang beberapa objek dan pustaka baru atau memperbaiki penggunaan dan pemahaman saya tentang implementasi yang ada yang sudah banyak digunakan dan berakhir dengan referensi yang layak untuk orang lain.

AJG85
sumber
5
Saya rasa ini harus diposting ulang sebagai jawaban atas pertanyaan ini, dan pertanyaan tersebut dibuat menjadi pertanyaan yang sebenarnya. Jika tidak, saya rasa orang akan menutup ini sebagai "bukan pertanyaan nyata".
strager
3
Ada berbagai macam petunjuk pintar lainnya, misalnya petunjuk pintar ATL atau OpenSceneGraph'sosg::ref_ptr .
James McNellis
11
Apa ada pertanyaan disini?
Cody Gray
6
Saya pikir Anda salah paham std::auto_ptr. std::auto_ptr_refadalah detail desain std::auto_ptr. std::auto_ptrtidak ada hubungannya dengan pengumpulan sampah, tujuan utamanya adalah secara khusus untuk memungkinkan pengecualian transfer kepemilikan yang aman, terutama dalam situasi pemanggilan fungsi dan pengembalian fungsi. std::unique_ptrhanya dapat memecahkan "masalah" yang Anda kutip dengan penampung standar karena C ++ telah berubah untuk memungkinkan perbedaan antara pemindahan dan penyalinan dan penampung standar telah berubah untuk memanfaatkan hal ini.
CB Bailey
3
Anda mengatakan bahwa Anda bukan ahli dalam petunjuk cerdas tetapi ringkasan Anda cukup lengkap, dan benar (kecuali untuk perdebatan kecil tentang auto_ptr_refmenjadi detail implementasi). Namun, saya setuju bahwa Anda harus memposting ini sebagai jawaban dan merumuskan ulang pertanyaan menjadi pertanyaan yang sebenarnya. Ini kemudian dapat berfungsi sebagai referensi di masa mendatang.
Konrad Rudolph

Jawaban:

231

C ++ 03

std::auto_ptr- Mungkin salah satu yang asli menderita sindroma draf pertama hanya karena fasilitas pengumpulan sampah terbatas. Kelemahan pertama adalah bahwa ia memanggil deletepemusnahan membuat mereka tidak dapat diterima untuk menampung array yang dialokasikan objek ( new[]). Ini mengambil kepemilikan penunjuk sehingga dua penunjuk otomatis tidak boleh berisi objek yang sama. Tugas akan mentransfer kepemilikan dan mengatur ulang penunjuk otomatis rvalue ke penunjuk nol. Yang mungkin mengarah pada kelemahan terburuk; mereka tidak dapat digunakan dalam kontainer STL karena ketidakmampuan untuk disalin. Pukulan terakhir untuk setiap kasus penggunaan adalah mereka dijadwalkan untuk tidak digunakan lagi dalam standar C ++ berikutnya.

std::auto_ptr_ref- Ini bukan penunjuk cerdas, ini sebenarnya adalah detail desain yang digunakan std::auto_ptruntuk memungkinkan penyalinan dan penugasan dalam situasi tertentu. Secara khusus ini dapat digunakan untuk mengubah non-const std::auto_ptrmenjadi nilai l menggunakan trik Colvin-Gibbons yang juga dikenal sebagai konstruktor pemindahan untuk mentransfer kepemilikan.

Sebaliknya, mungkin std::auto_ptrtidak benar-benar dimaksudkan untuk digunakan sebagai penunjuk cerdas tujuan umum untuk pengumpulan sampah otomatis. Sebagian besar pemahaman dan asumsi saya yang terbatas didasarkan pada Penggunaan Efektif Herb Sutter dari auto_ptr dan saya menggunakannya secara teratur meskipun tidak selalu dengan cara yang paling optimal.


C ++ 11

std::unique_ptr- Ini adalah teman kita yang akan menggantinya std::auto_ptrakan sangat mirip kecuali dengan perbaikan utama untuk memperbaiki kelemahan std::auto_ptrseperti bekerja dengan array, perlindungan lvalue melalui konstruktor salinan pribadi, dapat digunakan dengan kontainer dan algoritma STL, dll. Karena itu overhead kinerja dan jejak memori terbatas, ini adalah kandidat yang ideal untuk menggantikan, atau mungkin lebih tepat digambarkan sebagai memiliki, petunjuk mentah. Karena "unik" menyiratkan hanya ada satu pemilik penunjuk seperti sebelumnya std::auto_ptr.

std::shared_ptr- Saya yakin ini didasarkan pada TR1 dan boost::shared_ptrtetapi ditingkatkan untuk menyertakan aritmatika aliasing dan pointer juga. Singkatnya, ini membungkus referensi pointer pintar terhitung di sekitar objek yang dialokasikan secara dinamis. Karena "bersama" menyiratkan bahwa penunjuk dapat dimiliki oleh lebih dari satu penunjuk bersama ketika referensi terakhir dari penunjuk bersama terakhir keluar dari ruang lingkup maka objek akan dihapus dengan tepat. Ini juga aman untuk benang dan dapat menangani jenis yang tidak lengkap dalam banyak kasus. std::make_shareddapat digunakan untuk membuat std::shared_ptralokasi heap dengan satu heap secara efisien menggunakan pengalokasi default.

std::weak_ptr- Juga berdasarkan TR1 dan boost::weak_ptr. Ini adalah referensi ke objek yang dimiliki oleh a std::shared_ptrdan oleh karena itu tidak akan mencegah penghapusan objek jika jumlah std::shared_ptrreferensi turun ke nol. Untuk mendapatkan akses ke pointer mentah, pertama-tama Anda harus mengakses std::shared_ptrby calling lockyang akan mengembalikan kosong std::shared_ptrjika pointer yang dimiliki telah kedaluwarsa dan sudah dimusnahkan. Ini terutama berguna untuk menghindari penghitungan referensi gantung yang tidak terbatas saat menggunakan beberapa petunjuk cerdas.


Dorongan

boost::shared_ptr- Mungkin yang paling mudah digunakan dalam skenario yang paling bervariasi (STL, PIMPL, RAII, dll) ini adalah penunjuk cerdas terhitung yang dirujuk bersama. Saya telah mendengar beberapa keluhan tentang kinerja dan overhead dalam beberapa situasi tetapi saya pasti mengabaikannya karena saya tidak dapat mengingat apa argumennya. Rupanya itu cukup populer untuk menjadi objek C ++ standar yang tertunda dan tidak ada kelemahan atas norma mengenai petunjuk cerdas yang muncul di benak.

boost::weak_ptr- Sama seperti deskripsi sebelumnya tentang std::weak_ptr, berdasarkan implementasi ini, ini memungkinkan referensi yang tidak memiliki referensi untuk a boost::shared_ptr. Anda tidak mengherankan memanggil lock()untuk mengakses penunjuk bersama "kuat" dan harus memeriksa untuk memastikan itu valid karena bisa saja sudah dimusnahkan. Pastikan untuk tidak menyimpan penunjuk bersama yang dikembalikan dan membiarkannya keluar dari ruang lingkup segera setelah Anda selesai dengannya jika tidak, Anda akan langsung kembali ke masalah referensi siklik di mana jumlah referensi Anda akan hang dan objek tidak akan dimusnahkan.

boost::scoped_ptr- Ini adalah kelas penunjuk cerdas sederhana dengan sedikit overhead yang mungkin dirancang untuk alternatif yang berkinerja lebih baik daripada boost::shared_ptrsaat dapat digunakan. Ini sebanding dengan std::auto_ptrfakta bahwa itu tidak dapat digunakan dengan aman sebagai elemen dari wadah STL atau dengan banyak penunjuk ke objek yang sama.

boost::intrusive_ptr- Saya belum pernah menggunakan ini, tetapi dari pemahaman saya, ini dirancang untuk digunakan saat membuat kelas yang kompatibel dengan penunjuk cerdas Anda sendiri. Anda perlu menerapkan penghitungan referensi sendiri, Anda juga perlu menerapkan beberapa metode jika Anda ingin kelas Anda menjadi generik, selanjutnya Anda harus menerapkan keamanan utas Anda sendiri. Sisi positifnya, ini mungkin memberi Anda cara yang paling sesuai untuk memilih dan memilih dengan tepat seberapa banyak atau sedikit "kecerdasan" yang Anda inginkan. intrusive_ptrbiasanya lebih efisien daripada shared_ptrkarena memungkinkan Anda memiliki satu alokasi heap per objek. (terima kasih Arvid)

boost::shared_array- Ini boost::shared_ptruntuk array. Pada dasarnya new [],, operator[]dan tentu saja delete []dipanggang. Ini dapat digunakan dalam wadah STL dan sejauh yang saya tahu semuanya dapat boost:shared_ptrdilakukan meskipun Anda tidak dapat menggunakannya boost::weak_ptrdengan ini. Namun, Anda dapat menggunakan a boost::shared_ptr<std::vector<>>untuk fungsionalitas serupa dan mendapatkan kembali kemampuan untuk menggunakan boost::weak_ptrreferensi.

boost::scoped_array- Ini boost::scoped_ptruntuk array. Seperti dengan boost::shared_arraysemua kebaikan array yang diperlukan telah dimasukkan. Yang ini tidak dapat disalin sehingga tidak dapat digunakan dalam kontainer STL. Saya telah menemukan hampir di mana pun Anda ingin menggunakan ini, Anda mungkin bisa menggunakannya std::vector. Saya tidak pernah menentukan mana yang sebenarnya lebih cepat atau memiliki overhead yang lebih sedikit tetapi array cakupan ini tampaknya jauh lebih tidak terlibat daripada vektor STL. Saat Anda ingin menyimpan alokasi di tumpukan, pertimbangkan boost::arraysebagai gantinya.


Qt

QPointer- Diperkenalkan di Qt 4.0, ini adalah penunjuk pintar "lemah" yang hanya bekerja dengan QObjectdan kelas turunan, yang dalam kerangka kerja Qt hampir semuanya jadi itu sebenarnya bukan batasan. Namun ada batasan yaitu tidak menyediakan pointer yang "kuat" dan meskipun Anda dapat memeriksa apakah objek yang mendasari valid dengan isNull()Anda dapat menemukan objek Anda sedang dimusnahkan tepat setelah Anda melewati pemeriksaan itu terutama di lingkungan multi-threaded. Qt orang menganggap ini usang, saya percaya.

QSharedDataPointer- Ini adalah penunjuk cerdas "kuat" yang berpotensi sebanding dengan boost::intrusive_ptrmeskipun memiliki beberapa keamanan utas bawaan tetapi memerlukan Anda untuk menyertakan metode penghitungan referensi ( refdan deref) yang dapat Anda lakukan dengan membuat subkelas QSharedData. Seperti kebanyakan Qt, objek paling baik digunakan melalui pewarisan yang cukup dan subclassing semuanya tampaknya menjadi desain yang dimaksudkan.

QExplicitlySharedDataPointer- Sangat mirip dengan QSharedDataPointerkecuali itu tidak secara implisit memanggil detach(). Saya akan menyebut versi 2.0 ini QSharedDataPointerkarena sedikit peningkatan kontrol tentang kapan tepatnya harus melepaskan setelah jumlah referensi turun ke nol tidak terlalu berharga untuk objek yang sama sekali baru.

QSharedPointer- Penghitungan referensi atom, thread safe, pointer yang dapat dibagikan, penghapusan kustom (dukungan array), terdengar seperti segala sesuatu yang seharusnya menjadi pointer cerdas. Inilah yang terutama saya gunakan sebagai penunjuk pintar di Qt dan saya merasa sebanding dengan boost:shared_ptrmeskipun mungkin secara signifikan lebih banyak overhead seperti banyak objek di Qt.

QWeakPointer- Apakah Anda merasakan pola yang berulang? Sama seperti std::weak_ptrdan boost::weak_ptrini digunakan dalam hubungannya dengan QSharedPointersaat Anda membutuhkan referensi antara dua penunjuk cerdas yang sebaliknya akan menyebabkan objek Anda tidak akan pernah dihapus.

QScopedPointer- Nama ini juga harus terlihat akrab dan sebenarnya didasarkan pada boost::scoped_ptrtidak seperti versi Qt dari petunjuk bersama dan lemah. Ini berfungsi untuk menyediakan penunjuk cerdas pemilik tunggal tanpa overhead QSharedPointeryang membuatnya lebih cocok untuk kompatibilitas, kecuali kode aman, dan semua hal yang mungkin Anda gunakan std::auto_ptratau boost::scoped_ptruntuk.

AJG85
sumber
1
dua hal yang menurut saya patut disebutkan: intrusive_ptrbiasanya lebih efisien daripada shared_ptr, karena ini memungkinkan Anda memiliki alokasi heap tunggal per objek. shared_ptrdalam kasus umum akan mengalokasikan objek heap kecil yang terpisah untuk penghitung referensi. std::make_shareddapat digunakan untuk mendapatkan yang terbaik dari kedua dunia. shared_ptrdengan hanya satu alokasi heap.
Arvid
Saya memiliki pertanyaan yang mungkin tidak terkait: Dapatkah pengumpulan sampah diterapkan hanya dengan mengganti semua petunjuk dengan shared_ptrs? (Tidak termasuk menyelesaikan referensi siklik)
Seth Carnegie
@ Seth Carnegie: Tidak semua petunjuk akan mengarah ke sesuatu yang dialokasikan di toko gratis.
In silico
2
@the_mandrill Tetapi ini berfungsi jika destruktor dari kelas pemilik didefinisikan dalam unit terjemahan terpisah (.cpp-file) dari kode klien, yang dalam idiom Pimpl tetap diberikan. Karena unit terjemahan ini biasanya mengetahui definisi lengkap Pimpl dan oleh karena itu destruktornya (ketika menghancurkan auto_ptr) dengan benar menghancurkan Pimpl. Saya juga memiliki ketakutan untuk ini ketika saya melihat peringatan itu, tetapi saya mencobanya dan berhasil (destruktor Pimpl dipanggil). NB: tolong gunakan @ -syntax agar saya bisa melihat balasan.
Christian Rau
2
Kegunaan daftar ditingkatkan dengan menambahkan tautan yang sesuai ke dokumen.
ulidtko
1

Selain yang diberikan, ada juga beberapa yang berorientasi pada keselamatan:

SaferCPlusPlus

mse::TRefCountingPointeradalah referensi menghitung penunjuk pintar seperti std::shared_ptr. Perbedaannya mse::TRefCountingPointeradalah lebih aman, lebih kecil dan lebih cepat, tetapi tidak memiliki mekanisme pengaman benang. Dan itu datang dalam versi "not null" dan "fixed" (non-retargetable) yang dapat diasumsikan dengan aman untuk selalu mengarah ke objek yang dialokasikan secara valid. Jadi pada dasarnya, jika objek target Anda dibagikan di antara utas asinkron std::shared_ptr, gunakan , jika tidak mse::TRefCountingPointerakan lebih optimal.

mse::TScopeOwnerPointermirip dengan boost::scoped_ptr, tetapi bekerja bersama dengan mse::TScopeFixedPointerdalam hubungan penunjuk "kuat-lemah" seperti std::shared_ptrdan std::weak_ptr.

mse::TScopeFixedPointermenunjuk ke objek yang dialokasikan di tumpukan, atau yang penunjuk "miliknya" dialokasikan di tumpukan. Ini (sengaja) dibatasi dalam fungsinya untuk meningkatkan keamanan waktu kompilasi tanpa biaya runtime. Inti dari pointer "cakupan" pada dasarnya adalah untuk mengidentifikasi serangkaian keadaan yang sederhana dan cukup deterministik sehingga tidak diperlukan mekanisme keselamatan (runtime).

mse::TRegisteredPointerberperilaku seperti pointer mentah, kecuali nilainya secara otomatis disetel ke null_ptr saat objek target dihancurkan. Ini dapat digunakan sebagai pengganti umum untuk petunjuk mentah dalam banyak situasi. Seperti pointer mentah, itu tidak memiliki keamanan benang intrinsik. Namun sebagai gantinya, tidak ada masalah menargetkan objek yang dialokasikan di stack (dan mendapatkan manfaat kinerja yang sesuai). Ketika pemeriksaan run-time diaktifkan, penunjuk ini aman dari mengakses memori yang tidak valid. Karena mse::TRegisteredPointermemiliki perilaku yang sama dengan penunjuk mentah saat menunjuk ke objek yang valid, ia dapat "dinonaktifkan" (secara otomatis diganti dengan penunjuk mentah yang sesuai) dengan arahan waktu kompilasi, yang memungkinkannya digunakan untuk membantu menangkap bug dalam debug / uji / mode beta saat tidak menimbulkan biaya tambahan dalam mode rilis.

Berikut adalah artikel yang menjelaskan mengapa dan bagaimana menggunakannya. (Catatan, steker tidak tahu malu.)

Nuh
sumber