Inti dari shared_ptr
contoh yang berbeda adalah untuk menjamin (sejauh mungkin) bahwa selama ini shared_ptr
dalam 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_ptr
yang sama? Dan bagaimana jika itu kebetulan satu-satunya yang tersisa shared_ptr
dari 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::string
yang kemungkinan akan disalin saat diteruskan ke beberapa tempat, kami menggunakan shared_ptr
ke 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_message
adalah 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_message
untuk 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 msg
tidak pernah nol saat send_message
dipanggil.
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_ptr
terus 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_ptr
tidak 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_ptr
menjadi 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::string
seluruh 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.