std :: shared_ptr sebagai upaya terakhir?

59

Saya hanya menonton stream "Going Native 2012" dan saya memperhatikan diskusi tentang std::shared_ptr. Saya agak terkejut mendengar pandangan Bjarne yang agak negatif std::shared_ptrdan komentarnya bahwa itu harus digunakan sebagai "upaya terakhir" ketika waktu hidup suatu objek tidak pasti (yang saya percaya, menurutnya, jarang terjadi).

Adakah yang mau menjelaskan ini sedikit lebih dalam? Bagaimana kita bisa memprogram tanpa std::shared_ptrdan masih mengelola objek seumur hidup dengan cara yang aman ?

ronag
sumber
8
Tidak menggunakan pointer? Memiliki pemilik objek yang berbeda, yang mengelola kehidupan?
Bo Persson
2
bagaimana dengan data yang dibagikan secara eksplisit? Sulit untuk tidak menggunakan pointer. Juga std :: shared_pointer akan melakukan "kelola seumur hidup" kotor dalam kasus itu
Kamil Klimek
6
Sudahkah Anda mempertimbangkan untuk lebih sedikit mendengarkan saran yang disajikan dan lebih ke argumen di balik saran itu? Dia menjelaskan dengan baik jenis sistem di mana saran semacam ini akan bekerja.
Nicol Bolas
@NicolBolas: Saya mendengarkan saran dan argumen tetapi jelas saya tidak merasa saya cukup mengerti.
ronag
Jam berapa dia bilang "jalan terakhir"? Menonton bit pada 36 menit di ( channel9.msdn.com/Events/GoingNative/GoingNative-2012/… ) ia mengatakan bahwa ia waspada terhadap penggunaan pointer, tetapi ia juga berarti pointer secara umum, bukan hanya shared_ptr dan unique_ptr tetapi bahkan ' pointer biasa. Dia menyiratkan bahwa objek itu sendiri (dan bukan pointer ke objek yang dialokasikan dengan yang baru) harus lebih disukai. Apakah bagian yang Anda pikirkan nanti dalam presentasi?
Pharap

Jawaban:

55

Jika Anda dapat menghindari kepemilikan bersama maka aplikasi Anda akan lebih sederhana dan lebih mudah dipahami dan karenanya kurang rentan terhadap bug yang diperkenalkan selama pemeliharaan. Model kepemilikan yang kompleks atau tidak jelas cenderung mengarah pada sulit untuk mengikuti kopling dari berbagai bagian aplikasi melalui status bersama yang mungkin tidak mudah dilacak.

Mengingat hal ini, lebih disukai untuk menggunakan objek dengan durasi penyimpanan otomatis dan memiliki sub-objek "nilai". Kegagalan ini, unique_ptrmungkin merupakan alternatif yang baik dengan shared_ptrmenjadi - jika bukan pilihan terakhir - beberapa cara di bawah daftar alat yang diinginkan.

CB Bailey
sumber
5
+1 untuk menyatakan bahwa masalahnya bukan pada techno itu sendiri (kepemilikan bersama), tetapi kesulitan yang ditimbulkannya bagi kita manusia biasa yang kemudian harus menguraikan apa yang sedang terjadi.
Matthieu M.
Namun, mengambil pendekatan seperti itu akan sangat membatasi kemampuan programmer untuk menerapkan pola pemrograman konkurensi pada sebagian besar kelas OOP non-sepele (karena non-copyability.) Masalah ini diangkat dalam "Going Native 2013".
rwong
48

Dunia tempat Bjarne tinggal sangat ... akademis, karena menginginkan masa jabatan yang lebih baik. Jika kode Anda dapat dirancang dan disusun sedemikian rupa sehingga objek memiliki hierarki relasional yang disengaja, sehingga hubungan kepemilikan menjadi kaku dan pantang menyerah, kode mengalir dalam satu arah (dari level tinggi ke level rendah), dan objek hanya berbicara dengan yang lebih rendah di hierarki, maka Anda tidak akan menemukan banyak kebutuhan shared_ptr. Ini adalah sesuatu yang Anda gunakan pada kesempatan langka di mana seseorang harus melanggar aturan. Tetapi jika tidak, Anda bisa memasukkan semuanya ke dalam vectoratau struktur data lain yang menggunakan semantik nilai, dan unique_ptruntuk hal-hal yang harus Anda alokasikan sendiri.

Meskipun itu adalah dunia yang hebat untuk ditinggali, itu bukan apa yang bisa Anda lakukan sepanjang waktu. Jika Anda tidak dapat mengatur kode Anda dengan cara itu, karena desain sistem yang Anda coba buat berarti tidak mungkin (atau hanya sangat tidak menyenangkan), maka Anda akan menemukan diri Anda membutuhkan kepemilikan bersama atas benda-benda yang semakin banyak. .

Dalam sistem seperti itu, memegang pointer telanjang adalah ... tidak persis berbahaya, tetapi itu menimbulkan pertanyaan. Hal yang hebat tentang itu shared_ptradalah memberikan jaminan sintaksis yang wajar tentang masa hidup objek. Bisakah itu rusak? Tentu saja. Tetapi orang juga dapat melakukan banyak const_casthal; perawatan dasar dan pemberian makan shared_ptrharus memberikan kualitas hidup yang wajar untuk objek yang dialokasikan yang kepemilikannya harus dibagi.

Lalu, ada weak_ptrs, yang tidak dapat digunakan tanpa adanya a shared_ptr. Jika sistem Anda terstruktur dengan kaku, maka Anda dapat menyimpan pointer telanjang ke beberapa objek, aman karena mengetahui bahwa struktur aplikasi memastikan bahwa objek yang ditunjuk akan hidup lebih lama dari Anda. Anda dapat memanggil fungsi yang mengembalikan pointer ke beberapa nilai internal atau eksternal (misalnya, cari objek bernama X). Dalam kode yang terstruktur dengan baik, fungsi itu hanya akan tersedia untuk Anda jika umur objek dijamin melebihi milik Anda; dengan demikian, menyimpan pointer telanjang di objek Anda baik-baik saja.

Karena kekakuan itu tidak selalu mungkin dicapai dalam sistem nyata, Anda perlu beberapa cara untuk memastikan masa pakai yang wajar . Terkadang, Anda tidak perlu kepemilikan penuh; Terkadang, Anda hanya perlu tahu kapan pointer buruk atau bagus. Di situlah weak_ptrmasuk. Ada kasus di mana saya bisa menggunakan unique_ptratau boost::scoped_ptr, tapi saya harus menggunakan shared_ptrkarena saya secara khusus perlu memberi seseorang pointer "volatile". Pointer yang seumur hidupnya tidak pasti, dan mereka dapat menanyakan kapan pointer itu dihancurkan.

Cara aman untuk bertahan hidup ketika keadaan dunia tak tentu.

Mungkinkah itu dilakukan oleh beberapa pemanggilan fungsi untuk mendapatkan pointer, alih-alih via weak_ptr? Ya, tapi itu bisa lebih mudah dipatahkan. Fungsi yang mengembalikan pointer telanjang tidak memiliki cara yang menunjukkan secara sintaksis bahwa pengguna tidak melakukan sesuatu seperti menyimpan pointer itu dalam jangka panjang. Mengembalikan shared_ptrjuga membuatnya menjadi sangat mudah bagi seseorang untuk menyimpannya dan berpotensi memperpanjang masa hidup suatu objek. Mengembalikan yang weak_ptrsangat menyarankan bahwa menyimpan shared_ptrAnda dapatkan dari lockadalah ... ide yang meragukan. Ini tidak akan menghentikan Anda untuk melakukannya, tetapi tidak ada dalam C ++ yang menghentikan Anda dari memecahkan kode. weak_ptrmemberikan sedikit perlawanan dari melakukan hal yang alami.

Sekarang, itu bukan untuk mengatakan bahwa shared_ptrtidak bisa digunakan secara berlebihan ; itu pasti bisa. Terutama pra unique_ptr, ada banyak kasus di mana saya hanya menggunakan boost::shared_ptrkarena saya perlu melewati pointer RAII atau memasukkannya ke dalam daftar. Tanpa memindahkan semantik dan unique_ptr, boost::shared_ptradalah satu-satunya solusi nyata.

Dan Anda dapat menggunakannya di tempat-tempat yang sebenarnya tidak perlu. Seperti yang dinyatakan di atas, struktur kode yang tepat dapat menghilangkan kebutuhan untuk beberapa penggunaan shared_ptr. Tetapi jika sistem Anda tidak dapat terstruktur seperti itu dan masih melakukan apa yang perlu dilakukan, shared_ptrakan sangat bermanfaat.

Nicol Bolas
sumber
4
+1: Lihat misalnya di boost :: asio. Saya pikir idenya meluas ke banyak daerah, Anda mungkin tidak tahu pada waktu kompilasi widget UI atau panggilan asinkron mana yang terakhir melepaskan objek, dan dengan shared_ptr Anda tidak perlu tahu. Ini jelas tidak berlaku untuk setiap situasi, hanya alat (sangat berguna) di kotak alat.
Guy Sirton
3
Komentar agak terlambat; shared_ptrsangat bagus untuk sistem di mana c ++ terintegrasi dengan bahasa scripting seperti python. Menggunakan boost::python, penghitungan referensi pada sisi c ++ dan python sangat bekerja sama; objek apa pun dari c ++ masih bisa disimpan dalam python dan tidak akan mati.
eudoxos
1
Hanya untuk referensi, pemahaman saya bukanlah penggunaan WebKit atau Chromium shared_ptr. Keduanya menggunakan implementasi mereka sendiri intrusive_ptr. Saya hanya
membahasnya
1
@ gman: Saya menemukan komentar Anda sangat menyesatkan, karena keberatan Stroustrup shared_ptrberlaku sama untuk intrusive_ptr: dia keberatan dengan seluruh konsep kepemilikan bersama, bukan pada ejaan spesifik dari konsep tersebut. Jadi untuk tujuan pertanyaan ini, mereka adalah dua contoh-contoh nyata dari aplikasi besar yang tidak menggunakan shared_ptr. (Dan, lebih dari itu, mereka menunjukkan bahwa shared_ptritu berguna bahkan ketika itu tidak memungkinkan weak_ptr.)
ruakh
1
FWIW, untuk membantah klaim bahwa Bjarne hidup di dunia akademis: dalam semua karier murni industri saya (yang meliputi co-architecting sebuah bursa saham G20 dan semata-mata merancang MOG pemain 500K) Saya hanya melihat 3 kasus ketika kami benar-benar membutuhkan kepemilikan bersama. Saya 200% dengan Bjarne di sini.
No-Bugs Hare
37

Saya tidak percaya saya pernah menggunakannya std::shared_ptr.

Sebagian besar waktu, suatu objek dikaitkan dengan beberapa koleksi, yang menjadi miliknya selama seumur hidup. Dalam hal ini Anda hanya dapat menggunakan whatever_collection<o_type>atau whatever_collection<std::unique_ptr<o_type>>, koleksi itu menjadi anggota suatu objek atau variabel otomatis. Tentu saja, jika Anda tidak membutuhkan jumlah objek yang dinamis, Anda bisa menggunakan array otomatis ukuran tetap.

Baik iterasi melalui koleksi atau operasi lainnya pada objek tidak memerlukan fungsi pembantu untuk berbagi kepemilikan ... ia menggunakan objek, lalu kembali, dan penelepon menjamin bahwa objek tetap hidup untuk seluruh panggilan . Sejauh ini, ini adalah kontrak yang paling sering digunakan antara penelepon dan callee.


Nicol Bolas berkomentar bahwa "Jika suatu benda memegang penunjuk telanjang dan benda itu mati ... oops." dan "Objek perlu memastikan bahwa objek hidup melalui kehidupan objek itu. Hanya shared_ptrbisa melakukan itu."

Saya tidak membeli argumen itu. Setidaknya bukan itu yang shared_ptrmemecahkan masalah ini. Bagaimana dengan:

  • Jika beberapa tabel hash menampung suatu objek dan kode hash objek itu berubah ... oops.
  • Jika beberapa fungsi mengulang vektor dan elemen dimasukkan ke dalam vektor itu ... oops.

Seperti pengumpulan sampah, penggunaan default shared_ptrmendorong programmer untuk tidak memikirkan kontrak antara objek, atau antara fungsi dan pemanggil. Memikirkan prasyarat dan prasyarat yang benar diperlukan, dan objek seumur hidup hanyalah sepotong kecil dari pai yang lebih besar.

Objek tidak "mati", beberapa kode menghancurkannya. Dan melemparkan shared_ptrpada masalah alih-alih mencari tahu kontrak panggilan adalah keamanan yang salah.

Ben Voigt
sumber
17
@ronag: Saya menduga Anda sudah mulai menggunakannya di mana penunjuk mentah lebih baik, karena "penunjuk mentah buruk". Tapi pointer mentah tidak buruk . Hanya membuat yang pertama, memiliki pointer ke objek pointer mentah adalah buruk, karena Anda harus mengelola memori secara manual, yang non-sepele di hadapan pengecualian. Tetapi menggunakan pointer mentah sebagai pegangan atau iterator baik-baik saja.
Ben Voigt
4
@ BenVoigt: Tentu saja, kesulitan untuk membagikan pointer telanjang adalah Anda tidak tahu umur benda. Jika beberapa objek memegang pointer telanjang dan objek itu mati ... oops. Persis seperti itu shared_ptrdan weak_ptrdirancang untuk dihindari. Bjarne mencoba untuk hidup di dunia jika semuanya memiliki kehidupan yang baik, eksplisit, dan semuanya dibangun di sekitarnya. Dan jika Anda dapat membangun dunia itu, bagus. Tapi bukan itu yang terjadi di dunia nyata. Objek perlu memastikan bahwa objek hidup melalui kehidupan objek itu. Hanya shared_ptrbisa melakukan itu.
Nicol Bolas
5
@NicolBolas: Itu keamanan palsu. Jika pemanggil fungsi tidak memberikan jaminan seperti biasa: "Objek ini tidak akan disentuh oleh pihak luar selama pemanggilan fungsi" maka keduanya harus menyepakati jenis modifikasi luar apa yang diizinkan. shared_ptrhanya mengurangi satu modifikasi luar tertentu, dan bahkan tidak yang paling umum. Dan itu bukan tanggung jawab objek untuk memastikan masa pakainya benar, jika fungsi panggilan kontrak menentukan sebaliknya.
Ben Voigt
6
@NicolBolas: Jika suatu fungsi membuat objek dan mengembalikannya dengan pointer, itu harus berupa unique_ptr, yang menyatakan bahwa hanya satu pointer ke objek yang ada dan memiliki kepemilikan.
Ben Voigt
6
@Nicol: Jika mencari pointer di beberapa koleksi, mungkin harus menggunakan jenis pointer apa pun di koleksi itu, atau pointer mentah jika koleksi menyimpan nilai. Jika itu membuat objek dan penelepon menginginkan shared_ptr, itu masih harus mengembalikan a unique_ptr. Konversi dari unique_ptrke shared_ptrmudah, tetapi sebaliknya secara logis tidak mungkin.
Ben Voigt
16

Saya lebih suka tidak berpikir secara absolut (seperti "pilihan terakhir") tetapi relatif terhadap domain masalah.

C ++ dapat menawarkan sejumlah cara berbeda untuk mengatur masa pakai. Beberapa dari mereka mencoba untuk mengembalikan objek dengan cara yang digerakkan oleh tumpukan. Beberapa yang lain mencoba melepaskan diri dari batasan ini. Beberapa dari mereka adalah "literal", beberapa lainnya adalah perkiraan.

Sebenarnya Anda bisa:

  1. gunakan semantik nilai murni . Bekerja untuk benda yang relatif kecil di mana apa yang penting adalah "nilai-nilai" dan bukan "identitas", di mana Anda dapat mengasumsikan bahwa dua Personmemiliki yang sama nameadalah sama orang (lebih baik: dua representasi dari sama orang ). Seumur hidup diberikan oleh tumpukan mesin, pada akhirnya - tidak masalah bagi program (karena seseorang adalah nama , tidak peduli apa Personyang membawanya)
  2. gunakan tumpukan objek yang dialokasikan , dan referensi atau petunjuk terkait: memungkinkan polimorfisme, dan memberikan objek seumur hidup. Tidak perlu "smart pointer", karena Anda memastikan tidak ada objek yang dapat "diarahkan" oleh struktur yang meninggalkan tumpukan lebih lama dari objek yang mereka tuju (pertama buat objek, lalu struktur yang merujuknya).
  3. gunakan tumpukan dikelola heap dialokasikan objek : ini adalah apa yang std :: vector dan semua wadah lakukan, dan std::unique_ptrapa yang dilakukan (Anda dapat menganggapnya sebagai vektor dengan ukuran 1). Sekali lagi, Anda mengakui objek mulai ada (dan mengakhiri keberadaan mereka) sebelum (setelah) struktur data yang mereka rujuk.

Kelemahan dari mehtods ini adalah bahwa jenis dan jumlah objek tidak dapat bervariasi selama pelaksanaan panggilan level stack yang lebih dalam sehubungan dengan di mana mereka dibuat. Semua teknik ini "gagal" kekuatan mereka dalam semua situasi di mana penciptaan dan penghapusan objek adalah konsekuensi dari aktivitas pengguna, sehingga runtime-jenis objek tidak dikompilasi-waktu diketahui dan mungkin ada over-struktur yang merujuk pada objek yang pengguna diminta untuk menghapus dari panggilan fungsi tingkat-stack yang lebih dalam. Dalam hal ini, Anda harus:

  • memperkenalkan beberapa disiplin tentang mengelola objek dan struktur rujukan terkait atau ...
  • entah bagaimana pergi ke sisi gelap "melarikan diri seumur hidup berbasis tumpukan murni": objek harus pergi secara independen dari fungsi yang menciptakannya. Dan harus pergi ... sampai mereka dibutuhkan .

C ++ isteslf tidak memiliki mekanisme asli untuk memonitor acara itu ( while(are_they_needed)), maka Anda harus memperkirakan dengan:

  1. gunakan kepemilikan bersama : objek kehidupan terikat ke "referensi counter": berfungsi jika "kepemilikan" dapat diatur secara hierarkis, gagal jika loop kepemilikan mungkin ada. Inilah yang dilakukan std :: shared_ptr. Dan lemah_ptr dapat digunakan untuk memecah loop. Ini bekerja paling sering tetapi gagal dalam desain besar, di mana banyak desainer bekerja di tim yang berbeda dan tidak ada alasan yang jelas (sesuatu yang datang dari persyaratan yang agak) tentang siapa yang apak memiliki apa (contoh khas adalah rantai suka ganda: adalah sebelumnya karena yang berikutnya merujuk yang sebelumnya atau yang berikutnya yang memiliki yang sebelumnya merujuk yang berikutnya? Dalam ketiadaan persyaratan solusi tho adalah setara, dan dalam proyek besar Anda berisiko mencampurnya)
  2. Gunakan tumpukan pengumpulan sampah : Anda tidak peduli dengan masa hidup. Anda menjalankan kolektor dari waktu ke waktu dan apa yang tidak terjangkau dianggap "tidak diperlukan lagi" dan ... yah ... ahem ... hancur? selesai? beku?. Ada sejumlah kolektor GC, tetapi saya tidak pernah menemukan satu yang benar-benar sadar C ++. Sebagian besar dari mereka membebaskan memori, tidak peduli dengan penghancuran objek.
  3. Gunakan pengumpul sampah sadar C ++ , dengan antarmuka metode standar yang tepat. Semoga berhasil menemukannya.

Pergi ke solusi pertama ke yang terakhir, jumlah struktur data tambahan yang dibutuhkan untuk mengelola objek seumur hidup meningkat, karena waktu yang dihabiskan untuk mengatur dan memeliharanya.

Pengumpul sampah memiliki biaya, shared_ptr memiliki lebih sedikit, unique_ptr bahkan lebih sedikit, dan tumpukan objek yang dikelola memiliki sangat sedikit.

Apakah shared_ptr"jalan terakhir"? Tidak, tidak: jalan terakhir adalah pemulung. shared_ptrsebenarnya adalah upaya std::terakhir yang diusulkan. Tetapi mungkin solusi yang tepat, jika Anda berada dalam situasi yang saya jelaskan.

Emilio Garavaglia
sumber
9

Satu hal yang disebutkan oleh Herb Sutter di sesi berikutnya adalah bahwa setiap kali Anda menyalin shared_ptr<>ada kenaikan / penurunan yang saling terkait yang harus terjadi. Pada kode multi-utas pada sistem multi-inti, sinkronisasi memori tidak signifikan. Dengan pilihan itu, lebih baik menggunakan nilai tumpukan, atau a unique_ptr<>dan meneruskan referensi atau petunjuk mentah.

Gerhana
sumber
1
Atau berikan shared_ptrreferensi lvalue atau rvalue ...
ronag
8
Intinya adalah, jangan hanya menggunakan shared_ptrpeluru perak yang akan menyelesaikan semua masalah kebocoran memori Anda hanya karena sudah sesuai standar. Ini adalah jebakan yang menggoda, tetapi masih penting untuk menyadari kepemilikan sumber daya, dan kecuali kepemilikan itu dibagikan, a shared_ptr<>bukanlah pilihan terbaik.
Eclipse
Bagi saya ini adalah detail yang paling tidak penting. Lihat optimasi prematur. Dalam kebanyakan kasus, ini seharusnya tidak mendorong keputusan.
Guy Sirton
1
@ gbjbaanb: ya mereka ada di level cpu, tetapi pada sistem multi-core Anda membatalkan cache dan memaksa hambatan memori.
Eclipse
4
Dalam proyek gim yang saya kerjakan, kami menemukan bahwa perbedaan kinerja sangat signifikan, sampai pada titik di mana kami membutuhkan 2 jenis pointer penghitungan ulang yang berbeda, satu yang threadsafe, yang tidak.
Kylotan
7

Saya tidak ingat apakah "jalan" terakhir adalah kata yang tepat ia gunakan, tetapi saya percaya bahwa arti sebenarnya dari apa yang ia katakan adalah "pilihan" terakhir: diberikan syarat kepemilikan yang jelas; unique_ptr, kelemahan_ptr, shared_ptr dan bahkan pointer telanjang memiliki tempat mereka.

Satu hal yang mereka semua sepakati adalah bahwa kita (pengembang, penulis buku, dll) semuanya dalam "fase pembelajaran" C ++ 11 dan pola dan gaya didefinisikan.

Sebagai contoh, Herb menjelaskan kita harus mengharapkan edisi baru dari beberapa buku C ++ seminal, seperti C ++ Efektif (Meyers) dan C ++ Standar Pengkodean (Sutter & Alexandrescu), beberapa tahun lagi sementara pengalaman industri dan praktik terbaik dengan C ++ 11 panci keluar.

Eddie Velasquez
sumber
5

Saya pikir apa yang ia maksudkan adalah menjadi hal biasa bagi semua orang untuk menulis shared_ptr setiap kali mereka mungkin menulis pointer standar (seperti semacam penggantian global), dan itu digunakan sebagai cop-out alih-alih benar-benar mendesain atau setidaknya perencanaan untuk pembuatan dan penghapusan objek.

Hal lain yang orang lupa (selain bottleneck mengunci / memperbarui / membuka kunci yang disebutkan dalam materi di atas), adalah shared_ptr saja tidak menyelesaikan masalah siklus. Anda masih dapat membocorkan sumber daya dengan shared_ptr:

Objek A, berisi pointer bersama ke Objek A lainnya. Objek B membuat A a1 dan A a2, dan menetapkan a1.otherA = a2; dan a2.otherA = a1; Sekarang, pointer bersama objek B yang digunakan untuk membuat a1, a2 keluar dari ruang lingkup (katakanlah pada akhir fungsi). Sekarang Anda memiliki kebocoran - tidak ada orang lain yang mengacu pada a1 dan a2, tetapi mereka merujuk satu sama lain sehingga jumlah referensi mereka selalu 1, dan Anda telah bocor.

Itu contoh sederhana, ketika ini terjadi dalam kode nyata itu terjadi biasanya dengan cara yang rumit. Ada solusi dengan lemah_ptr, tetapi begitu banyak orang sekarang hanya melakukan shared_ptr di mana-mana dan bahkan tidak tahu masalah kebocoran atau bahkan soal lemah_ptr.

Untuk menyelesaikannya: Saya pikir komentar yang dirujuk oleh OP bermuara pada ini:

Tidak peduli bahasa apa yang Anda gunakan (dikelola, tidak dikelola, atau sesuatu di antaranya dengan jumlah referensi seperti shared_ptr), Anda perlu memahami dan secara sengaja memutuskan pembuatan objek, masa hidup, dan penghancuran.

sunting: bahkan jika itu berarti "tidak diketahui, saya perlu menggunakan shared_ptr," Anda masih memikirkannya dan melakukannya dengan sengaja.

segera
sumber
3

Saya akan menjawab dari pengalaman saya dengan Objective-C, bahasa di mana semua objek referensi dihitung dan dialokasikan di heap. Karena memiliki satu cara untuk memperlakukan objek, banyak hal lebih mudah bagi programmer. Itu telah memungkinkan aturan standar untuk didefinisikan bahwa, ketika dipatuhi, menjamin kekokohan kode dan tidak ada kebocoran memori. Itu juga memungkinkan untuk optimisasi kompiler pintar untuk muncul seperti ARC baru-baru ini (penghitungan referensi otomatis).

Maksud saya adalah shared_ptr harus menjadi pilihan pertama Anda daripada pilihan terakhir. Gunakan penghitungan referensi secara default dan opsi lain hanya jika Anda yakin dengan apa yang Anda lakukan. Anda akan lebih produktif dan kode Anda akan lebih kuat.

Dimitris
sumber
1

Saya akan mencoba menjawab pertanyaan:

Bagaimana kita bisa memprogram tanpa std :: shared_ptr dan masih mengatur umur objek dengan cara yang aman?

C ++ memiliki sejumlah besar cara berbeda untuk melakukan memori, misalnya:

  1. Gunakan struct A { MyStruct s1,s2; };alih-alih shared_ptr dalam lingkup kelas. Ini hanya untuk pemrogram tingkat lanjut karena mengharuskan Anda memahami cara kerja dependensi, dan membutuhkan kemampuan untuk mengontrol dependensi yang cukup untuk membatasi mereka ke pohon. Urutan kelas dalam file header adalah aspek penting dari ini. Tampaknya penggunaan ini sudah umum dengan tipe bawaan bawaan ++, tetapi penggunaannya dengan kelas yang ditentukan pemrogram tampaknya kurang digunakan karena masalah ketergantungan dan urutan kelas ini. Solusi ini juga memiliki masalah dengan sizeof. Programmer melihat masalah dalam hal ini sebagai persyaratan untuk menggunakan deklarasi maju atau #includes yang tidak perlu dan dengan demikian banyak programmer akan kembali ke solusi inferior dari pointer dan kemudian ke shared_ptr.
  2. Gunakan MyClass &find_obj(int i);+ clone () sebagai ganti shared_ptr<MyClass> create_obj(int i);. Banyak programmer ingin membuat pabrik untuk membuat objek baru. shared_ptr sangat cocok untuk penggunaan seperti ini. Masalahnya adalah bahwa itu sudah mengasumsikan solusi manajemen memori yang kompleks menggunakan heap / alokasi toko bebas, bukan tumpukan sederhana atau solusi berbasis objek. Hirarki kelas C ++ yang baik mendukung semua skema manajemen memori, bukan hanya satu saja. Solusi berbasis referensi dapat bekerja jika objek yang dikembalikan disimpan di dalam objek yang mengandung, alih-alih menggunakan variabel lingkup fungsi lokal. Melewati kepemilikan dari pabrik ke kode pengguna harus dihindari. Menyalin objek setelah menggunakan find_obj () adalah cara yang baik untuk menanganinya - copy-constructor normal dan constructor normal (dari kelas yang berbeda) dengan parameter refrerence atau clone () agar objek polimorfik dapat menanganinya.
  3. Gunakan referensi sebagai ganti pointer atau shared_ptrs. Setiap kelas c ++ memiliki konstruktor, dan setiap anggota data referensi perlu diinisialisasi. Penggunaan ini dapat menghindari banyak penggunaan pointer dan shared_ptrs. Anda hanya perlu memilih apakah ingatan Anda ada di dalam objek, atau di luarnya, dan memilih solusi struct atau solusi referensi berdasarkan keputusan. Masalah dengan solusi ini biasanya terkait dengan menghindari parameter konstruktor yang umum tetapi praktik bermasalah dan kesalahpahaman bagaimana antarmuka untuk kelas harus dirancang.
tp1
sumber
"Melewati kepemilikan dari pabrik ke kode pengguna harus dihindari." Dan apa yang terjadi ketika itu tidak mungkin? "Gunakan referensi alih-alih pointer atau shared_ptrs." Um, tidak. Pointer bisa diulang. Referensi tidak bisa. Ini memaksa pembatasan waktu konstruksi pada apa yang disimpan di kelas. Itu tidak praktis untuk banyak hal. Solusi Anda tampaknya sangat kaku dan tidak fleksibel dengan kebutuhan antarmuka yang lebih lancar dan pola penggunaan.
Nicol Bolas
@Nicol Bolas: Setelah Anda mengikuti aturan di atas, referensi akan digunakan untuk dependensi antar objek, dan bukan untuk penyimpanan data seperti yang Anda sarankan. Ketergantungan lebih stabil daripada data, jadi kami tidak pernah masuk ke masalah yang Anda pertimbangkan.
tp1
Berikut ini contoh yang sangat sederhana. Anda memiliki entitas permainan, yang merupakan objek. Perlu merujuk ke objek lain, yang merupakan entitas target yang perlu diajak bicara. Namun, target bisa berubah. Target bisa mati di berbagai titik. Dan entitas harus mampu menangani keadaan ini. Pendekatan tanpa-pointer kaku Anda tidak dapat menangani bahkan sesuatu yang sederhana seperti mengubah target, apalagi target sekarat.
Nicol Bolas
@nicol bolas: oh, itu ditangani secara berbeda; antarmuka kelas mendukung lebih dari satu "entitas". Alih-alih pemetaan 1: 1 antara objek dan entitas, Anda akan menggunakan entitasarray. Kemudian entitas mati dengan sangat mudah hanya dengan menghapusnya dari array. Hanya ada sejumlah kecil susunan entitas di seluruh permainan dan ketergantungan antar array tidak terlalu sering berubah :)
tp1
2
Tidak, unique_ptrpaling cocok untuk pabrik. Anda dapat mengubah a unique_ptrmenjadi shared_ptr, tetapi secara logis tidak mungkin untuk pergi ke arah lain.
Ben Voigt