Seperti yang saya lihat, smart pointer digunakan secara luas di banyak proyek C ++ dunia nyata.
Meskipun beberapa jenis pointer cerdas jelas bermanfaat untuk mendukung RAII dan transfer kepemilikan, ada juga kecenderungan menggunakan pointer bersama secara default , sebagai cara "pengumpulan sampah" , sehingga pemrogram tidak harus memikirkan alokasi sebanyak itu. .
Mengapa pointer bersama lebih populer daripada mengintegrasikan pengumpul sampah yang tepat seperti Boehm GC ? (Atau apakah Anda setuju sama sekali, bahwa mereka lebih populer daripada GC yang sebenarnya?)
Saya tahu dua keunggulan GC konvensional dibandingkan penghitungan referensi:
- Algoritma GC konvensional tidak memiliki masalah dengan siklus referensi .
- Referensi-hitungan umumnya lebih lambat dari GC yang tepat.
Apa alasan untuk menggunakan smart pointer penghitungan referensi?
c++
garbage-collection
Miklós Homolya
sumber
sumber
std::unique_ptr
sudah cukup dan karena itu memiliki overhead nol atas pointer mentah dalam hal kinerja run-time. Dengan menggunakan distd::shared_ptr
mana - mana Anda juga akan mengaburkan semantik kepemilikan, kehilangan salah satu manfaat utama dari pointer pintar selain manajemen sumber daya otomatis - pemahaman yang jelas tentang maksud di balik kode.Jawaban:
Beberapa keuntungan penghitungan referensi dibandingkan pengumpulan sampah:
Overhead rendah. Pengumpul sampah bisa sangat mengganggu (mis. Membuat program Anda macet di waktu yang tidak dapat diprediksi saat siklus pengumpulan sampah) dan cukup intensif terhadap memori (mis. Jejak memori proses Anda yang tidak perlu tumbuh hingga beberapa megabyte sebelum pengumpulan sampah akhirnya dimulai)
Perilaku yang lebih mudah ditebak. Dengan penghitungan referensi, Anda dijamin bahwa objek Anda akan dibebaskan saat referensi terakhir hilang. Dengan pengumpulan sampah, di sisi lain, objek Anda akan dibebaskan "kapan", ketika sistem berhasil melakukannya. Untuk RAM ini biasanya bukan masalah besar pada desktop atau server yang sedikit dimuat, tetapi untuk sumber daya lain (mis. File menangani) Anda sering membutuhkan mereka ditutup SECEPATNYA untuk menghindari potensi konflik di kemudian hari.
Lebih sederhana. Penghitungan referensi dapat dijelaskan dalam beberapa menit, dan diterapkan dalam satu atau dua jam. Pengumpul sampah, terutama yang memiliki kinerja baik, sangat kompleks dan tidak banyak orang yang memahaminya.
Standar. C ++ termasuk penghitungan referensi (melalui shared_ptr) dan teman-teman di STL, yang berarti bahwa sebagian besar programmer C ++ sudah mengenalnya dan sebagian besar kode C ++ akan bekerja dengannya. Namun, tidak ada pengumpul sampah C ++ standar, yang artinya Anda harus memilih satu dan berharap itu berfungsi dengan baik untuk kasus penggunaan Anda - dan jika tidak, itu masalah Anda untuk diperbaiki, bukan bahasa.
Adapun dugaan kerugian penghitungan referensi - tidak mendeteksi siklus adalah masalah, tapi salah satu yang saya tidak pernah bertemu secara pribadi dalam sepuluh tahun terakhir menggunakan penghitungan referensi. Sebagian besar struktur data bersifat asiklikal alami, dan jika Anda menemukan situasi di mana Anda memerlukan referensi siklikal (mis. Pointer orangtua di simpul pohon), Anda bisa menggunakan pelemahan_ptr atau pointer C mentah untuk "arah mundur". Selama Anda mengetahui masalah potensial saat Anda merancang struktur data Anda, itu bukan masalah.
Adapun kinerja, saya tidak pernah punya masalah dengan kinerja penghitungan referensi. Saya memiliki masalah dengan kinerja pengumpulan sampah, khususnya pembekuan acak yang dapat dilakukan oleh GC, di mana satu-satunya solusi ("jangan mengalokasikan objek") mungkin juga diulangi dengan "jangan gunakan GC" .
sumber
make_shared
ketika kembali. Meski demikian, latensi cenderung menjadi masalah yang lebih besar dalam aplikasi waktu nyata, tetapi throughput lebih penting secara umum, itulah sebabnya pelacakan GC sangat banyak digunakan. Saya tidak akan begitu cepat untuk berbicara buruk tentang mereka.Untuk mendapatkan kinerja yang baik dari GC, GC harus dapat memindahkan objek dalam memori. Dalam bahasa seperti C ++ di mana Anda dapat berinteraksi langsung dengan lokasi memori, ini sangat mustahil. (Microsoft C ++ / CLR tidak masuk hitungan karena memperkenalkan sintaks baru untuk pointer yang dikelola GC dan karenanya secara efektif merupakan bahasa yang berbeda.)
Boehm GC, meskipun ide yang bagus, sebenarnya adalah yang terburuk dari kedua dunia: Anda membutuhkan malloc () yang lebih lambat daripada GC yang baik, dan karenanya Anda kehilangan perilaku alokasi / deallokasi yang deterministik tanpa peningkatan kinerja yang sesuai dari GC generasi . Ditambah lagi dengan keharusan konservatif, jadi tidak harus mengumpulkan semua sampah Anda.
GC yang baik dan selaras bisa menjadi hal yang hebat. Tetapi dalam bahasa seperti C ++, keuntungannya minimal dan biayanya sering tidak sepadan.
Akan menarik untuk melihat, bagaimanapun, ketika C ++ 11 menjadi lebih populer, apakah lambda dan semantik tangkap mulai memimpin komunitas C ++ ke arah masalah alokasi dan objek seumur hidup yang sama yang menyebabkan komunitas Lisp menciptakan GCs pada awalnya tempat.
Lihat juga jawaban saya untuk pertanyaan terkait di StackOverflow .
sumber
Benar tetapi, secara objektif, sebagian besar kode sekarang ditulis dalam bahasa modern dengan melacak pengumpul sampah.
Itu ide yang buruk karena Anda masih perlu khawatir tentang siklus.
Oh wow, ada banyak hal yang salah dengan garis pemikiran Anda:
Boehm's GC bukanlah GC yang "tepat" dalam arti kata apa pun. Benar-benar mengerikan. Itu konservatif sehingga bocor dan tidak efisien dengan desain. Lihat: http://flyingfrogblog.blogspot.co.uk/search/label/boehm
Pointer bersama, secara objektif, jauh dari tempat yang sama populernya dengan GC karena sebagian besar pengembang menggunakan bahasa GC'd sekarang dan tidak memerlukan pointer bersama. Lihat saja Java dan Javascript di pasar kerja dibandingkan dengan C ++.
Anda tampaknya membatasi pertimbangan untuk C ++ karena, saya berasumsi, Anda berpikir bahwa GC adalah masalah tangensial. Ini bukan ( satu - satunya cara untuk mendapatkan GC yang layak adalah merancang bahasa dan VM untuk itu sejak awal) sehingga Anda memperkenalkan bias seleksi. Orang-orang yang benar-benar menginginkan pengumpulan sampah yang tepat tidak bertahan dengan C ++.
Anda dibatasi untuk C ++ tetapi berharap Anda memiliki manajemen memori otomatis.
sumber
Di MacOS X dan iOS, dan dengan pengembang yang menggunakan Objective-C atau Swift, penghitungan referensi populer karena ditangani secara otomatis, dan penggunaan pengumpulan sampah sangat menurun karena Apple tidak mendukungnya lagi (saya diberi tahu bahwa aplikasi menggunakan pengumpulan sampah akan pecah di versi MacOS X berikutnya, dan pengumpulan sampah tidak pernah diterapkan di iOS). Sebenarnya saya benar-benar ragu bahwa ada banyak perangkat lunak yang menggunakan pengumpulan sampah ketika tersedia.
Alasan membuang pengumpulan sampah: Itu tidak pernah bekerja dengan baik di lingkungan bergaya C di mana pointer bisa "melarikan diri" ke daerah-daerah yang tidak dapat diakses oleh pengumpul sampah. Apple sangat percaya dan percaya bahwa penghitungan referensi lebih cepat. (Anda dapat membuat klaim apa pun di sini tentang kecepatan relatif, tetapi tidak ada yang dapat meyakinkan Apple). Dan pada akhirnya, tidak ada yang menggunakan pengumpulan sampah.
Hal pertama yang dipelajari pengembang MacOS X atau iOS adalah bagaimana menangani siklus referensi, jadi itu bukan masalah bagi pengembang nyata.
sumber
Kerugian terbesar dari pengumpulan sampah di C ++ adalah, sangat tidak mungkin untuk memperbaikinya:
Dalam C ++, pointer tidak hidup di komunitas mereka sendiri, mereka dicampur dengan data lain. Dengan demikian, Anda tidak dapat membedakan pointer dari data lain yang kebetulan memiliki pola bit yang dapat diartikan sebagai pointer yang valid.
Konsekuensi: Setiap pengumpul sampah C ++ akan membocorkan objek yang harus dikumpulkan.
Di C ++, Anda bisa melakukan pointer aritmatika untuk mendapatkan pointer. Dengan demikian, jika Anda tidak menemukan pointer ke awal blok, itu tidak berarti bahwa blok itu tidak dapat dirujuk.
Konsekuensi: Setiap pengumpul sampah C ++ harus memperhitungkan penyesuaian ini, memperlakukan setiap urutan bit yang terjadi ke titik mana pun dalam blok, termasuk tepat setelah akhir, sebagai penunjuk yang valid yang merujuk pada blok.
Catatan: Tidak ada pengumpul sampah C ++ yang dapat menangani kode dengan trik seperti ini:
Benar, ini memunculkan perilaku yang tidak terdefinisi. Tetapi beberapa kode yang ada lebih pintar dari pada yang baik untuk itu, dan mungkin memicu pemindahan lokasi awal oleh seorang pemulung.
sumber