Petunjuk cerdas: siapa yang memiliki objek tersebut? [Tutup]

114

C ++ adalah tentang kepemilikan memori - alias semantik kepemilikan .

Ini adalah tanggung jawab pemilik sebagian dari memori yang dialokasikan secara dinamis untuk melepaskan memori itu. Jadi pertanyaannya sebenarnya menjadi siapa yang memiliki ingatan.

Dalam C ++, kepemilikan didokumentasikan oleh jenis pointer mentah yang dibungkus di dalamnya sehingga dalam program C ++ yang baik (IMO) sangat jarang ( jarang , tidak pernah ) untuk melihat pointer mentah diedarkan (karena pointer mentah tidak memiliki kepemilikan yang disimpulkan sehingga kita bisa tidak memberi tahu siapa yang memiliki memori dan dengan demikian tanpa membaca dokumentasi dengan cermat, Anda tidak dapat mengetahui siapa yang bertanggung jawab atas kepemilikan).

Sebaliknya, sangat jarang untuk melihat pointer mentah disimpan di kelas setiap pointer mentah disimpan dalam bungkus smart pointer-nya sendiri. ( NB: Jika Anda tidak memiliki objek, Anda tidak boleh menyimpannya karena Anda tidak dapat mengetahui kapan objek itu akan keluar dari ruang lingkup dan dimusnahkan.)

Jadi pertanyaannya:

  • Jenis kepemilikan semantik apa yang sering ditemukan orang?
  • Kelas standar apa yang digunakan untuk mengimplementasikan semantik tersebut?
  • Dalam situasi apa Anda menganggapnya berguna?

Mari kita pertahankan 1 jenis kepemilikan semantik per jawaban sehingga dapat dipilih naik turun secara individual.

Ringkasan:

Secara konseptual, petunjuk cerdas itu sederhana dan implementasi yang naif itu mudah. Saya telah melihat banyak penerapan percobaan, tetapi selalu gagal dalam beberapa cara yang tidak jelas untuk penggunaan dan contoh biasa. Oleh karena itu, saya sarankan untuk selalu menggunakan petunjuk cerdas yang teruji dengan baik dari perpustakaan daripada menggulirkannya sendiri. std::auto_ptratau salah satu petunjuk cerdas Boost sepertinya memenuhi semua kebutuhan saya.

std::auto_ptr<T>:

Orang lajang memiliki objek tersebut. Transfer kepemilikan diperbolehkan.

Penggunaan: Ini memungkinkan Anda untuk menentukan antarmuka yang menunjukkan transfer kepemilikan secara eksplisit.

boost::scoped_ptr<T>

Orang lajang memiliki objek tersebut. Transfer kepemilikan TIDAK diperbolehkan.

Penggunaan: Digunakan untuk menunjukkan kepemilikan eksplisit. Objek akan dihancurkan oleh destruktor atau saat disetel ulang secara eksplisit.

boost::shared_ptr<T>( std::tr1::shared_ptr<T>)

Kepemilikan ganda. Ini adalah referensi pointer terhitung sederhana. Saat jumlah referensi mencapai nol, objek dimusnahkan.

Kegunaan: Ketika suatu objek dapat memiliki banyak bunga dengan masa pakai yang tidak dapat ditentukan pada waktu kompilasi.

boost::weak_ptr<T>:

Digunakan dengan shared_ptr<T>situasi di mana siklus petunjuk mungkin terjadi.

Penggunaan: Digunakan untuk menghentikan siklus dari mempertahankan objek ketika hanya siklus yang mempertahankan refcount bersama.

Martin York
sumber
14
?? Apa pertanyaannya?
Pacerier
9
Saya hanya ingin menunjukkan bahwa sejak pertanyaan ini diposting, auto_ptr sudah tidak digunakan lagi dan mendukung unique_ptr (yang sekarang distandarisasi)
Juan Campa
In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) Bisakah ini diubah? Saya tidak mengerti sama sekali.
lolololol ol
@lololololol Anda memotong kalimat menjadi dua. In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good C++ program it is very rare to see RAW pointers passed around. Penunjuk RAW tidak memiliki semantik kepemilikan. Jika Anda tidak mengetahui pemiliknya, Anda tidak tahu siapa yang bertanggung jawab untuk menghapus objek. Ada beberapa kelas standar yang digunakan untuk membungkus pointer (std :: shared_ptr, std :: unique_ptr dll) yang mendefinisikan kepemilikan dan karenanya tentukan siapa yang bertanggung jawab untuk menghapus penunjuk.
Martin York
1
Di C ++ 11 + JANGAN GUNAKAN auto_ptr! Gunakan unique_ptr sebagai gantinya!
val mengatakan Reinstate Monica

Jawaban:

20

Bagi saya, 3 jenis ini memenuhi sebagian besar kebutuhan saya:

shared_ptr - referensi dihitung, deallocation saat penghitung mencapai nol

weak_ptr- sama seperti di atas, tetapi ini adalah 'budak' untuk a shared_ptr, tidak dapat membatalkan alokasi

auto_ptr- saat pembuatan dan pelepasan alokasi terjadi di dalam fungsi yang sama, atau saat objek harus dianggap hanya satu-pemilik. Saat Anda menetapkan satu penunjuk ke penunjuk lainnya, penunjuk kedua 'mencuri' objek dari penunjuk pertama.

Saya memiliki penerapan sendiri untuk ini, tetapi mereka juga tersedia di Boost.

Saya masih meneruskan objek dengan referensi ( constbila memungkinkan), dalam hal ini metode yang dipanggil harus menganggap objek tersebut hidup hanya selama waktu panggilan.

Ada jenis pointer lain yang saya gunakan yang saya sebut hub_ptr . Itu ketika Anda memiliki objek yang harus dapat diakses dari objek yang bersarang di dalamnya (biasanya sebagai kelas basis virtual). Ini bisa diselesaikan dengan memberikan weak_ptrkepada mereka, tetapi tidak harus shared_ptritu sendiri. Karena ia tahu objek ini tidak akan hidup lebih lama darinya, ia meneruskan hub_ptr ke mereka (itu hanya pembungkus template ke penunjuk biasa).

Fabio Ceconello
sumber
2
Alih-alih membuat kelas pointer Anda sendiri (hub_ptr), mengapa Anda tidak meneruskan * this ke objek-objek ini dan membiarkan mereka menyimpannya sebagai referensi? Karena kamu bahkan mengakui bahwa benda-benda itu akan dihancurkan pada saat yang sama dengan kelas pemilik, aku tidak mengerti gunanya melompati begitu banyak rintangan.
Michel
4
Ini pada dasarnya adalah kontrak desain untuk memperjelas semuanya. Saat objek anak menerima hub_ptr, ia tahu bahwa objek yang diarahkan tidak akan dihancurkan selama masa hidup anak, dan tidak memiliki kepemilikan atasnya. Objek yang terkandung dan wadah setuju dengan seperangkat aturan yang jelas. Jika Anda menggunakan pointer telanjang, aturan dapat didokumentasikan, tetapi tidak akan diberlakukan oleh kompilator dan kode.
Fabio Ceconello
1
Perhatikan juga bahwa Anda dapat memiliki #ifdefs untuk membuat hub_ptr diketikef ke pointer telanjang dalam build rilis, jadi overhead hanya akan ada di build debug.
Fabio Ceconello
3
Perhatikan bahwa dokumentasi Boost bertentangan dengan deskripsi scoped_ptr Anda. Ini menyatakan bahwa itu noncopyabledan kepemilikan tidak dapat dialihkan.
Alec Thomas
3
@ Alec Thomas, Anda benar. Saya berpikir tentang auto_ptr dan menulis scoped_ptr. Diperbaiki.
Fabio Ceconello
23

Model C ++ sederhana

Di sebagian besar modul yang saya lihat, secara default, diasumsikan bahwa penerima petunjuk tidak menerima kepemilikan. Faktanya, fungsi / metode yang mengabaikan kepemilikan pointer sangat jarang dan secara eksplisit mengungkapkan fakta tersebut dalam dokumentasinya.

Model ini mengasumsikan bahwa pengguna adalah pemilik hanya dari apa yang dia alokasikan secara eksplisit . Segala sesuatu yang lain secara otomatis dibuang (saat keluar ruang lingkup, atau melalui RAII). Ini adalah model mirip-C, diperluas oleh fakta bahwa kebanyakan pointer dimiliki oleh objek yang akan membatalkan alokasinya secara otomatis atau ketika dibutuhkan (pada penghancuran objek tersebut, sebagian besar), dan bahwa durasi hidup objek dapat diprediksi (RAII adalah teman Anda, lagi).

Dalam model ini, petunjuk mentah beredar bebas dan sebagian besar tidak berbahaya (tetapi jika pengembang cukup pintar, dia akan menggunakan referensi sebagai gantinya bila memungkinkan).

  • petunjuk mentah
  • std :: auto_ptr
  • boost :: scoped_ptr

Model C ++ Menunjuk Cerdas

Dalam kode yang penuh dengan petunjuk cerdas, pengguna dapat berharap untuk mengabaikan masa pakai objek. Pemilik tidak pernah menjadi kode pengguna: Ini adalah penunjuk cerdas itu sendiri (RAII, sekali lagi). Masalahnya adalah bahwa referensi melingkar yang dicampur dengan referensi yang dihitung sebagai petunjuk pintar bisa mematikan , jadi Anda harus berurusan dengan petunjuk bersama dan petunjuk lemah. Jadi Anda masih memiliki kepemilikan untuk dipertimbangkan (penunjuk yang lemah bisa jadi tidak menunjukkan apa-apa, bahkan jika keuntungannya dibandingkan penunjuk mentah adalah ia dapat memberitahu Anda demikian).

  • boost :: shared_ptr
  • meningkatkan :: weak_ptr

Kesimpulan

Tidak peduli model yang saya gambarkan, kecuali, menerima pointer tidak menerima kepemilikannya dan masih sangat penting untuk mengetahui siapa yang memiliki siapa . Bahkan untuk kode C ++ banyak menggunakan referensi dan / atau petunjuk cerdas.

paercebal.dll
sumber
10

Tidak memiliki kepemilikan bersama. Jika Anda melakukannya, pastikan hanya dengan kode yang tidak Anda kontrol.

Itu menyelesaikan 100% masalah, karena memaksa Anda untuk memahami bagaimana segala sesuatu berinteraksi.

MSN
sumber
2
  • Kepemilikan Bersama
  • boost :: shared_ptr

Saat sumber daya dibagikan di antara beberapa objek. Peningkatan shared_ptr menggunakan penghitungan referensi untuk memastikan sumber daya dibatalkan alokasi ketika semua orang selesai.

Martin York
sumber
2

std::tr1::shared_ptr<Blah> sering kali merupakan taruhan terbaik Anda.

Matt Cruikshank
sumber
2
shared_ptr adalah yang paling umum. Tapi masih banyak lagi. Masing-masing memiliki pola penggunaan dan tempat baik dan buruk untuk menuntut. Sedikit lebih banyak deskripsi akan menyenangkan.
Martin York
Jika Anda terjebak dengan kompiler lama, boost :: shared_ptr <blah> adalah dasar dari std :: tr1 :: shared_ptr <blah>. Ini adalah kelas yang cukup sederhana sehingga Anda mungkin dapat menyalinnya dari Boost dan menggunakannya bahkan jika kompiler Anda tidak didukung oleh versi terbaru Boost.
Branan
2

Dari boost, ada juga wadah penunjuk pustaka . Ini sedikit lebih efisien dan lebih mudah digunakan daripada wadah standar pointer pintar, jika Anda hanya akan menggunakan objek dalam konteks wadahnya.

Di Windows, terdapat penunjuk COM (IUnknown, IDispatch, dan teman), dan berbagai penunjuk cerdas untuk menanganinya (misalnya CComPtr ATL dan penunjuk pintar yang dihasilkan secara otomatis oleh pernyataan "impor" di Visual Studio berdasarkan kelas _com_ptr ).

Ryan Ginstrom
sumber
1
  • Satu Pemilik
  • boost :: scoped_ptr

Saat Anda perlu mengalokasikan memori secara dinamis tetapi ingin memastikannya dialokasikan pada setiap titik keluar blok.

Saya menemukan ini berguna karena dapat dengan mudah diulang, dan dirilis tanpa harus khawatir tentang kebocoran

Pieter
sumber
1

Saya rasa saya tidak pernah berada dalam posisi untuk berbagi kepemilikan dalam desain saya. Faktanya, dari atas kepala saya satu-satunya kasus valid yang dapat saya pikirkan adalah pola Kelas Terbang.

Nemanja Trifunovic
sumber
1

yasper :: ptr adalah alternatif yang ringan, boost :: shared_ptr. Ini bekerja dengan baik dalam proyek kecil saya (untuk saat ini).

Di halaman web di http://yasper.sourceforge.net/ dijelaskan sebagai berikut:

Mengapa menulis penunjuk cerdas C ++ lainnya? Sudah ada beberapa implementasi penunjuk pintar berkualitas tinggi untuk C ++, yang paling menonjol adalah panteon penunjuk Boost dan SmartPtr Loki. Untuk perbandingan yang baik dari implementasi penunjuk cerdas dan ketika penggunaannya sesuai, baca The New C ++: Smart (er) Pointers dari Herb Sutter. Berbeda dengan fitur ekspansif library lain, Yasper adalah pointer penghitungan referensi yang fokusnya sempit. Ini berhubungan erat dengan kebijakan shared_ptr Boost dan RefCounted / AllowConversion Loki. Yasper memungkinkan programmer C ++ untuk melupakan manajemen memori tanpa memperkenalkan dependensi besar Boost atau harus belajar tentang template kebijakan Loki yang rumit. Filsafat

* small (contained in single header)
* simple (nothing fancy in the code, easy to understand)
* maximum compatibility (drop in replacement for dumb pointers)

Poin terakhir bisa berbahaya, karena yasper mengizinkan tindakan berisiko (namun berguna) (seperti penugasan ke petunjuk mentah dan rilis manual) yang tidak diizinkan oleh implementasi lain. Hati-hati, hanya gunakan fitur tersebut jika Anda tahu apa yang Anda lakukan!

Hernán
sumber
1

Ada bentuk lain yang sering digunakan dari pemilik tunggal yang dapat dialihkan, dan ini lebih disukai auto_ptrkarena menghindari masalah yang disebabkan oleh auto_ptrkorupsi semantik tugas yang tidak wajar.

Saya berbicara tidak lain adalah swap. Jenis apa pun dengan swapfungsi yang sesuai dapat dipahami sebagai referensi cerdas untuk beberapa konten, yang dimilikinya hingga saat kepemilikan ditransfer ke instance lain dari jenis yang sama, dengan menukarnya. Setiap instance mempertahankan identitasnya tetapi terikat ke konten baru. Ini seperti referensi yang dapat ditarik kembali dengan aman.

(Ini adalah referensi cerdas daripada penunjuk cerdas karena Anda tidak perlu secara eksplisit membedakannya untuk mendapatkan konten.)

Ini berarti auto_ptr menjadi kurang diperlukan - ini hanya diperlukan untuk mengisi celah di mana tipe tidak memiliki swapfungsi yang baik . Tetapi semua kontainer standar melakukannya.

Daniel Earwicker
sumber
Mungkin itu menjadi kurang diperlukan (menurut saya scoped_ptr membuatnya kurang penting dari ini), tetapi itu tidak akan hilang. Memiliki fungsi swap tidak membantu Anda sama sekali jika Anda mengalokasikan sesuatu di heap dan seseorang melempar sebelum Anda menghapusnya, atau Anda lupa.
Michel
Itulah yang saya katakan di paragraf terakhir.
Daniel Earwicker
0
  • Satu Pemilik: Aka hapus pada Salinan
  • std :: auto_ptr

Ketika pencipta objek ingin secara eksplisit menyerahkan kepemilikan kepada orang lain. Ini juga cara mendokumentasikan kode yang saya berikan kepada Anda dan saya tidak lagi melacaknya, jadi pastikan Anda menghapusnya setelah selesai.

Martin York
sumber