Pointer jenis apa yang saya gunakan saat?

228

Ok, jadi terakhir kali saya menulis C ++ untuk mencari nafkah, std::auto_ptradalah semua std lib telah tersedia, dan boost::shared_ptrsemua adalah kemarahan. Saya tidak pernah benar-benar melihat ke dalam tipe penunjuk pintar lainnya yang disediakan. Saya mengerti bahwa C ++ 11 sekarang menyediakan beberapa tipe boost, tetapi tidak semuanya.

Jadi apakah seseorang memiliki algoritma sederhana untuk menentukan kapan harus menggunakan smart pointer? Lebih disukai termasuk saran tentang pointer bodoh (pointer mentah seperti T*) dan sisa pointer pintar meningkatkan. (Sesuatu seperti ini akan bagus).

sbi
sumber
Lihat juga std :: auto_ptr ke std :: unique_ptr
Martin York
1
Saya benar-benar berharap seseorang membuat flowchart berguna yang bagus seperti diagram alur pilihan STL ini .
Alok Simpan
1
@Als: Oh, itu memang bagus! Saya menanyai itu.
sbi
6
@Dupuplikator Itu bahkan tidak hampir menjadi duplikat. Pertanyaannya terkait mengatakan "Ketika saya harus menggunakan sebuah pointer pintar" dan pertanyaan ini adalah "Kapan saya menggunakan ini pointer pintar?" yaitu yang satu ini mengkategorikan berbagai penggunaan dari smart pointer standar. Pertanyaan yang ditautkan tidak melakukan hal ini. Perbedaannya tampaknya kecil tetapi itu besar.
Rapptz

Jawaban:

183

Kepemilikan bersama:
Standar shared_ptrdan weak_ptryang diadopsi hampir sama dengan rekan Boost mereka . Gunakan mereka ketika Anda perlu berbagi sumber daya dan tidak tahu mana yang akan menjadi yang terakhir untuk hidup. Gunakan weak_ptruntuk mengamati sumber daya bersama tanpa mempengaruhi masa pakainya, bukan untuk memutus siklus. Siklus dengan shared_ptrseharusnya tidak terjadi - dua sumber daya tidak dapat memiliki satu sama lain.

Perhatikan bahwa Boost juga menawarkan shared_array, yang mungkin merupakan alternatif yang cocok untuk shared_ptr<std::vector<T> const>.

Selanjutnya, Boost menawarkan intrusive_ptr, yang merupakan solusi ringan jika sumber daya Anda sudah menawarkan manajemen yang dihitung referensi dan Anda ingin mengadopsinya dengan prinsip RAII. Yang ini tidak diadopsi oleh standar.

Kepemilikan unik:
Boost juga memiliki scoped_ptr, yang tidak dapat disalin dan Anda tidak dapat menentukan deleter. std::unique_ptradalah boost::scoped_ptrpada steroid dan harus Anda pilihan default ketika Anda membutuhkan pointer pintar . Ini memungkinkan Anda untuk menentukan deleter dalam argumen templatnya dan dapat dipindah , tidak seperti boost::scoped_ptr. Ini juga sepenuhnya dapat digunakan dalam wadah STL selama Anda tidak menggunakan operasi yang membutuhkan jenis yang dapat disalin (jelas).

Perhatikan lagi, bahwa Boost memiliki versi larik:, scoped_arrayyang standar disatukan dengan memerlukan std::unique_ptr<T[]>spesialisasi parsial yang akan delete[]mengarahkan bukan penunjuk delete(dengan default_deleter). std::unique_ptr<T[]>juga menawarkan operator[]bukan operator*dan operator->.

Perhatikan bahwa std::auto_ptrmasih dalam standar, tetapi sudah usang . §D.10 [depr.auto.ptr]

Templat kelas auto_ptrsudah usang. [ Catatan: Templat kelas unique_ptr(20.7.1) memberikan solusi yang lebih baik. —Kirim catatan ]

Tanpa kepemilikan:
Gunakan pointer bodoh (pointer mentah) atau referensi untuk referensi yang tidak memiliki sumber daya dan ketika Anda tahu bahwa sumber daya akan hidup lebih lama dari objek referensi / ruang lingkup. Lebih suka referensi dan gunakan pointer mentah ketika Anda membutuhkan nullability atau resettability.

Jika Anda ingin referensi yang tidak memiliki sumber daya, tetapi Anda tidak tahu apakah sumber daya akan hidup lebih lama dari objek yang mereferensikannya, kemas sumber daya itu dalam shared_ptrdan gunakan a weak_ptr- Anda dapat menguji apakah induknya shared_ptrmasih hidup lock, yang akan mengembalikan shared_ptryang bukan nol jika sumber daya masih ada. Jika ingin menguji apakah sumber daya sudah mati, gunakan expired. Keduanya mungkin terdengar serupa, tetapi sangat berbeda dalam menghadapi eksekusi bersamaan, karena expiredhanya menjamin nilai kembalinya untuk pernyataan tunggal itu. Tes yang tampaknya tidak bersalah seperti

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

adalah kondisi lomba yang potensial.

Xeo
sumber
1
Dalam hal tidak ada kepemilikan, Anda mungkin lebih suka referensi ke pointer kecuali Anda tidak perlu kepemilikan dan resettability di mana referensi tidak akan memotongnya, bahkan kemudian Anda mungkin ingin mempertimbangkan menulis ulang objek asli menjadi shared_ptrdan pointer yang tidak memiliki menjadi a weak_ptr...
David Rodríguez - dribeas
2
Saya tidak bermaksud merujuk ke pointer , melainkan referensi alih-alih pointer. Jika tidak ada kepemilikan, kecuali jika Anda memerlukan resettability (atau nullability, tetapi nullability tanpa dapat mengatur ulang akan sangat terbatas), Anda dapat menggunakan referensi biasa daripada pointer di tempat pertama.
David Rodríguez - dribeas
1
@ David: Ah, begitu. :) Ya, referensi tidak buruk untuk itu, saya pribadi lebih suka mereka juga dalam kasus seperti itu. Saya akan menambahkannya.
Xeo
1
@ Xeo: shared_array<T>adalah alternatif untuk shared_ptr<T[]>tidak shared_ptr<vector<T>>: itu tidak bisa tumbuh.
R. Martinho Fernandes
1
@GregroyCurrie: Itu ... apa yang saya tulis? Saya mengatakan ini adalah contoh kondisi lomba yang potensial.
Xeo
127

Menentukan penunjuk pintar apa yang akan digunakan adalah masalah kepemilikan . Ketika datang ke manajemen sumber daya, objek A memiliki objek B jika ia mengendalikan masa objek B. Misalnya, variabel anggota dimiliki oleh objek masing-masing karena masa hidup variabel anggota terkait dengan masa pakai objek. Anda memilih pointer pintar berdasarkan pada bagaimana objek dimiliki.

Perhatikan bahwa kepemilikan dalam sistem perangkat lunak terpisah dari kepemilikan karena kami akan menganggapnya di luar perangkat lunak. Misalnya, seseorang mungkin "memiliki" rumah mereka, tetapi itu tidak berarti bahwa suatu Personobjek memiliki kendali atas masa hidup suatu Houseobjek. Menggabungkan konsep-konsep dunia nyata ini dengan konsep perangkat lunak adalah cara yang pasti untuk memprogram diri Anda menjadi sebuah lubang.


Jika Anda memiliki kepemilikan tunggal atas objek tersebut, gunakan std::unique_ptr<T>.

Jika Anda telah berbagi kepemilikan atas objek ...
- Jika tidak ada siklus kepemilikan, gunakan std::shared_ptr<T>.
- Jika ada siklus, tentukan "arah" dan gunakan std::shared_ptr<T>dalam satu arah dan std::weak_ptr<T>lainnya.

Jika objek memiliki Anda, tetapi ada kemungkinan tidak memiliki pemilik, gunakan pointer normal T*(misal pointer orangtua).

Jika objek memiliki Anda (atau keberadaannya dijamin), gunakan referensi T&.


Peringatan: Waspadai biaya petunjuk pintar. Dalam lingkungan terbatas memori atau kinerja, mungkin bermanfaat untuk hanya menggunakan pointer normal dengan skema yang lebih manual untuk mengelola memori.

Biaya:

  • Jika Anda memiliki deleter khusus (mis. Anda menggunakan kumpulan alokasi) maka ini akan menimbulkan overhead per pointer yang dapat dengan mudah dihindari dengan penghapusan manual.
  • std::shared_ptrmemiliki overhead dari kenaikan jumlah referensi pada salinan, ditambah penurunan pada penghancuran diikuti oleh cek 0-hitungan dengan penghapusan objek yang ditahan. Bergantung pada implementasinya, ini dapat mengasapi kode Anda dan menyebabkan masalah kinerja.
  • Waktu kompilasi. Seperti semua templat, pointer cerdas berkontribusi negatif terhadap waktu kompilasi.

Contoh:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

Pohon biner tidak memiliki induknya, tetapi keberadaan pohon menyiratkan keberadaan induknya (atau nullptruntuk root), sehingga menggunakan pointer normal. Pohon biner (dengan semantik nilai) memiliki kepemilikan tunggal atas anak-anaknya, demikian pula mereka std::unique_ptr.

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

Di sini, simpul daftar memiliki daftar berikutnya dan sebelumnya, jadi kami menentukan arah dan digunakan shared_ptruntuk selanjutnya dan weak_ptruntuk mematahkan siklus.

Peter Alexander
sumber
3
Sebagai contoh pohon biner beberapa orang akan menyarankan menggunakan shared_ptr<BinaryTree>untuk anak-anak dan weak_ptr<BinaryTree>untuk hubungan orang tua.
David Rodríguez - dribeas
@ DavidRodríguez-dribeas: Tergantung apakah Tree memiliki nilai semantik atau tidak. Jika orang akan mereferensikan pohon Anda secara eksternal bahkan setelah pohon sumber dihancurkan maka ya, kombo penunjuk bersama / lemah akan menjadi yang terbaik.
Peter Alexander
Jika suatu benda memiliki Anda dan dijamin ada maka mengapa bukan referensi.
Martin York
1
Jika Anda menggunakan referensi, Anda tidak akan pernah bisa mengubah induknya, yang mungkin atau mungkin tidak menghalangi desain. Untuk menyeimbangkan pohon, itu akan menghalangi.
Mooing Duck
3
+1 tetapi Anda harus menambahkan definisi "kepemilikan" di baris pertama. Saya sering menemukan diri saya harus dengan jelas menyatakan bahwa ini tentang hidup & mati objek, bukan kepemilikan dalam makna yang lebih spesifik-domain.
Klaim
19

Gunakan unique_ptr<T>sepanjang waktu kecuali ketika Anda membutuhkan penghitungan referensi, dalam hal ini penggunaan shared_ptr<T>(dan untuk kasus yang sangat jarang, weak_ptr<T>untuk mencegah siklus referensi). Dalam hampir setiap kasus, kepemilikan unik yang dapat ditransfer tidak masalah.

Pointer mentah: Bagus hanya jika Anda membutuhkan pengembalian kovarian, penunjukan tidak memiliki yang bisa terjadi. Sebaliknya mereka tidak terlalu berguna.

Array pointer: unique_ptrmemiliki spesialisasi T[]yang secara otomatis memanggil delete[]hasilnya, sehingga Anda dapat melakukannya dengan aman unique_ptr<int[]> p(new int[42]);misalnya. shared_ptrAnda masih memerlukan deleter khusus, tetapi Anda tidak memerlukan pointer array khusus yang dibagikan atau unik. Tentu saja, hal-hal seperti itu biasanya paling baik diganti std::vector. Sayangnya shared_ptrtidak menyediakan fungsi akses array, jadi Anda masih harus menelepon secara manual get(), tetapi unique_ptr<T[]>menyediakan operator[]alih-alih operator*dan operator->. Bagaimanapun, Anda harus memeriksa diri Anda sendiri. Ini membuat shared_ptrsedikit kurang ramah pengguna, meskipun bisa dibilang keunggulan generik dan tidak ada ketergantungan Boost membuat unique_ptrdan shared_ptrpemenang lagi.

Petunjuk lingkup: Dibuat tidak relevan oleh unique_ptr, sama seperti auto_ptr.

Tidak ada yang lebih dari itu. Dalam C ++ 03 tanpa memindahkan semantik situasi ini sangat rumit, tetapi dalam C ++ 11 sarannya sangat sederhana.

Masih ada penggunaan untuk pointer pintar lainnya, seperti intrusive_ptratau interprocess_ptr. Namun, mereka sangat niche dan sama sekali tidak perlu dalam kasus umum.

Anak anjing
sumber
Juga, petunjuk mentah untuk iterasi. Dan untuk buffer parameter output, di mana buffer dimiliki oleh pemanggil.
Ben Voigt
Hmm, cara saya membaca itu, itu adalah situasi yang merupakan kovarian kembali dan tidak memiliki. Menulis ulang mungkin baik jika Anda maksudkan persatuan daripada persimpangan. Saya juga akan mengatakan bahwa iterasi juga patut disebutkan secara khusus.
Ben Voigt
2
std::unique_ptr<T[]>menyediakan operator[]bukan operator*dan operator->. Memang benar bahwa Anda masih perlu melakukan pengecekan diri sendiri.
Xeo
8

Kasus kapan harus digunakan unique_ptr:

  • Metode pabrik
  • Anggota yang pointer (termasuk mucikari)
  • Menyimpan pointer dalam contl stl (untuk menghindari gerakan)
  • Penggunaan objek dinamis lokal yang besar

Kasus kapan harus digunakan shared_ptr:

  • Berbagi objek di utas
  • Berbagi objek secara umum

Kasus kapan harus digunakan weak_ptr:

  • Peta besar yang bertindak sebagai referensi umum (mis. Peta semua soket terbuka)

Merasa bebas untuk mengedit dan menambahkan lebih banyak

Lalaland
sumber
Saya sebenarnya lebih menyukai jawaban Anda saat Anda memberikan skenario.
Nicholas Humphrey