Inti dari shared_ptrcontoh yang berbeda adalah untuk menjamin (sejauh mungkin) bahwa selama ini shared_ptrdalam ruang lingkup, objek yang ditunjuknya akan tetap ada, karena jumlah referensinya paling sedikit 1.
Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
// sp points to an object that cannot be destroyed during this function
}
Jadi dengan menggunakan referensi ke a shared_ptr, Anda menonaktifkan jaminan itu. Jadi dalam kasus kedua Anda:
Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here
{
...
sp->do_something();
...
}
Bagaimana Anda tahu bahwa sp->do_something()tidak akan meledak karena pointer nol?
Itu semua tergantung apa yang ada di bagian '...' dari kode itu. Bagaimana jika Anda memanggil sesuatu selama '...' pertama yang memiliki efek samping (di suatu tempat di bagian lain dari kode) untuk membersihkan objek shared_ptryang sama? Dan bagaimana jika itu kebetulan satu-satunya yang tersisa shared_ptrdari objek itu? Bye bye object, tepat di mana Anda akan mencoba dan menggunakannya.
Jadi ada dua cara untuk menjawab pertanyaan itu:
Periksa sumber seluruh program Anda dengan sangat hati-hati sampai Anda yakin objek tidak akan mati selama badan fungsi.
Ubah parameter kembali menjadi objek berbeda, bukan referensi.
Sedikit saran umum yang berlaku di sini: jangan repot-repot membuat perubahan berisiko pada kode Anda demi kinerja sampai Anda mengatur waktu produk Anda dalam situasi yang realistis di profiler dan secara meyakinkan mengukur bahwa perubahan yang ingin Anda buat akan membuat perbedaan yang signifikan terhadap kinerja.
Pembaruan untuk pemberi komentar JQ
Inilah contoh yang dibuat-buat. Ini sengaja dibuat sederhana, jadi kesalahannya akan terlihat jelas. Dalam contoh nyata, kesalahan tidak begitu jelas karena tersembunyi dalam lapisan detail nyata.
Kami memiliki fungsi yang akan mengirim pesan ke suatu tempat. Ini mungkin pesan besar jadi daripada menggunakan std::stringyang kemungkinan akan disalin saat diteruskan ke beberapa tempat, kami menggunakan shared_ptrke string:
void send_message(std::shared_ptr<std::string> msg)
{
std::cout << (*msg.get()) << std::endl;
}
(Kami hanya "mengirim" ke konsol untuk contoh ini).
Sekarang kami ingin menambahkan fasilitas untuk mengingat pesan sebelumnya. Kami menginginkan perilaku berikut: variabel harus ada yang berisi pesan terkirim terbaru, tetapi saat pesan sedang dikirim maka tidak boleh ada pesan sebelumnya (variabel harus disetel ulang sebelum dikirim). Jadi kami mendeklarasikan variabel baru:
std::shared_ptr<std::string> previous_message;
Kemudian kami mengubah fungsi kami sesuai dengan aturan yang kami tentukan:
void send_message(std::shared_ptr<std::string> msg)
{
previous_message = 0;
std::cout << *msg << std::endl;
previous_message = msg;
}
Jadi, sebelum kita mulai mengirim kita membuang pesan sebelumnya yang sekarang, dan kemudian setelah pengiriman selesai kita bisa menyimpan pesan baru sebelumnya. Semuanya bagus. Berikut beberapa kode tes:
send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);
Dan seperti yang diharapkan, ini mencetak Hi!dua kali.
Sekarang datanglah Tuan Pemelihara, yang melihat kode dan berpikir: Hei, parameter itu send_messageadalah a shared_ptr:
void send_message(std::shared_ptr<std::string> msg)
Jelas itu bisa diubah menjadi:
void send_message(const std::shared_ptr<std::string> &msg)
Pikirkan peningkatan kinerja yang akan dihasilkannya! (Jangan pedulikan bahwa kami akan mengirim pesan yang biasanya berukuran besar melalui beberapa saluran, sehingga peningkatan kinerja akan menjadi sangat kecil sehingga tidak dapat diukur).
Tetapi masalah sebenarnya adalah bahwa sekarang kode pengujian akan menunjukkan perilaku tidak terdefinisi (dalam Visual C ++ 2010 debug build, ia lumpuh).
Tn. Maintainer terkejut dengan hal ini, tetapi menambahkan pemeriksaan defensif send_messageuntuk menghentikan terjadinya masalah:
void send_message(const std::shared_ptr<std::string> &msg)
{
if (msg == 0)
return;
Tapi tentu saja itu masih berjalan dan macet, karena msgtidak pernah nol saat send_messagedipanggil.
Seperti yang saya katakan, dengan semua kode yang begitu berdekatan dalam contoh yang sepele, mudah untuk menemukan kesalahannya. Tetapi dalam program nyata, dengan hubungan yang lebih kompleks antara objek yang bisa berubah yang menyimpan pointer satu sama lain, mudah untuk membuat kesalahan, dan sulit untuk membangun kasus uji yang diperlukan untuk mendeteksi kesalahan.
Solusi mudah, di mana Anda ingin sebuah fungsi dapat mengandalkan shared_ptrterus menjadi bukan nol secara keseluruhan, adalah fungsi mengalokasikan true-nya sendiri shared_ptr, daripada mengandalkan referensi ke yang sudah ada shared_ptr.
Sisi negatifnya adalah bahwa menyalin a shared_ptrtidak gratis: bahkan implementasi "tanpa kunci" harus menggunakan operasi yang saling bertautan untuk menghormati jaminan threading. Jadi, mungkin ada situasi di mana program dapat dipercepat secara signifikan dengan mengubah a shared_ptrmenjadi shared_ptr &. Tetapi ini bukanlah perubahan yang dapat dilakukan dengan aman untuk semua program. Ini mengubah arti logis dari program.
Perhatikan bahwa bug serupa akan terjadi jika kita menggunakan std::stringseluruh alih-alih std::shared_ptr<std::string>, dan alih-alih:
previous_message = 0;
untuk menghapus pesan tersebut, kami berkata:
previous_message.clear();
Kemudian gejalanya adalah pengiriman pesan kosong yang tidak disengaja, bukan perilaku yang tidak ditentukan. Biaya salinan tambahan dari string yang sangat besar mungkin jauh lebih signifikan daripada biaya menyalin a shared_ptr, jadi trade-offnya mungkin berbeda.