C ++ - meneruskan referensi ke std :: shared_ptr atau boost :: shared_ptr

115

Jika saya memiliki fungsi yang perlu bekerja dengan a shared_ptr, bukankah akan lebih efisien untuk memberikan referensi padanya (jadi untuk menghindari penyalinan shared_ptrobjek)? Apa saja kemungkinan efek samping yang buruk? Saya membayangkan dua kemungkinan kasus:

1) di dalam fungsi salinan dibuat dari argumen, seperti di

ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)  
{  
     ...  
     m_sp_member=sp; //This will copy the object, incrementing refcount  
     ...  
}  

2) di dalam fungsi argumen hanya digunakan, seperti di

Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here  
{    
    ...  
    sp->do_something();  
    ...  
}  

Saya tidak dapat melihat dalam kedua kasus alasan yang baik untuk meneruskan nilai boost::shared_ptr<foo>by alih-alih referensi. Meneruskan nilai hanya akan menambah jumlah referensi "sementara" karena penyalinan, dan kemudian menurunkannya saat keluar dari cakupan fungsi. Apakah saya mengabaikan sesuatu?

Hanya untuk memperjelas, setelah membaca beberapa jawaban: Saya sangat setuju dengan masalah pengoptimalan prematur, dan saya selalu mencoba untuk membuat profil-pertama-kemudian-bekerja-di-hotspot. Pertanyaan saya lebih dari sudut pandang kode teknis murni, jika Anda tahu apa yang saya maksud.

abigagli
sumber
Saya tidak tahu apakah Anda dapat mengubah tag pertanyaan Anda, tapi coba tambahkan tag pendorong di sana. Saya mencoba mencari pertanyaan ini tetapi saya tidak dapat menemukannya karena saya mencari tag boost dan smart-pointer. Jadi saya menemukan pertanyaan Anda tepat setelah membuat pertanyaan saya sendiri
Edison Gustavo Muenz

Jawaban:

113

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:

  1. Periksa sumber seluruh program Anda dengan sangat hati-hati sampai Anda yakin objek tidak akan mati selama badan fungsi.

  2. 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.

Daniel Earwicker
sumber
16
Shared_ptr yang diteruskan sudah berada dalam suatu cakupan, di situs panggilan. Anda mungkin dapat membuat skenario yang rumit di mana kode dalam pertanyaan ini akan meledak karena penunjuk yang menjuntai, tetapi saya kira Anda memiliki masalah yang lebih besar daripada parameter referensi!
Magnus Hoff
10
Mungkin disimpan di anggota. Anda dapat menelepon sesuatu yang terjadi untuk membersihkan anggota itu. Inti dari smart_ptr adalah untuk menghindari keharusan mengoordinasikan masa pakai dalam hierarki atau cakupan yang bersarang di sekitar tumpukan panggilan, jadi itulah mengapa sebaiknya berasumsi bahwa masa hidup tidak melakukannya dalam program semacam itu.
Daniel Earwicker
7
Ini bukan sudut pandang saya! Jika Anda berpikir apa yang saya katakan adalah sesuatu yang spesifik untuk dilakukan dengan kode saya, Anda mungkin tidak mengerti saya. Saya berbicara tentang implikasi yang tidak dapat dihindari dari alasan shared_ptr ada di tempat pertama: banyak masa pakai objek tidak hanya terkait dengan panggilan fungsi.
Daniel Earwicker
8
@DanielEarwicker sepenuhnya setuju dengan semua poin Anda dan saya terkejut dengan tingkat oposisi. Sesuatu yang membuat kekhawatiran Anda menjadi lebih relevan adalah threading, ketika ini menjadi terlibat jaminan tentang validitas objek menjadi jauh lebih penting. Jawaban yang bagus.
radman
3
Belum lama ini, saya mengejar bug yang sangat serius yang disebabkan oleh referensi ke penunjuk bersama. Kode tersebut menangani perubahan status objek dan ketika mengetahui status objek telah berubah, kode tersebut menghapusnya dari kumpulan objek di status sebelumnya dan memindahkannya ke kumpulan objek di status baru. Operasi penghapusan menghancurkan penunjuk bersama terakhir ke objek. Fungsi anggota telah dipanggil dengan referensi ke penunjuk bersama dalam koleksi. Ledakan. Daniel Earwicker benar.
David Schwartz
115

Saya mendapati diri saya tidak setuju dengan jawaban yang dipilih paling banyak, jadi saya mencari pendapat ahli dan ini dia. Dari http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2011-Scott-Andrei-and-Herb-Ask-Us-Anything

Herb Sutter: "jika Anda meneruskan shared_ptrs, salinan menjadi mahal"

Scott Meyers: "Tidak ada yang istimewa tentang shared_ptr dalam hal apakah Anda meneruskannya dengan nilai, atau meneruskannya dengan referensi. Gunakan analisis yang sama persis dengan yang Anda gunakan untuk jenis yang ditentukan pengguna lainnya. Orang-orang tampaknya memiliki persepsi ini bahwa shared_ptr entah bagaimana memecahkannya semua masalah manajemen, dan karena itu kecil, maka murah untuk melewatkan nilai. Itu harus disalin, dan ada biaya yang terkait dengan itu ... mahal untuk menyebarkannya berdasarkan nilai, jadi jika saya bisa lolos dengan semantik yang tepat dalam program saya, saya akan meneruskannya dengan merujuk ke const atau referensi sebagai gantinya "

Herb Sutter: "selalu meneruskannya dengan referensi ke const, dan terkadang mungkin karena Anda tahu apa yang Anda panggil dapat mengubah hal yang Anda dapatkan referensi, mungkin Anda akan melewatkan nilai ... jika Anda menyalinnya sebagai parameter, oh Ya ampun, Anda hampir tidak perlu melebihi jumlah referensi itu karena toh disimpan hidup-hidup, dan Anda harus meneruskannya dengan referensi, jadi tolong lakukan itu "

Pembaruan: Herb telah memperluas ini di sini: http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ , meskipun moral dari ceritanya adalah Anda tidak boleh lewat shared_ptrs sama sekali "kecuali jika Anda ingin menggunakan atau memanipulasi smart pointer itu sendiri, seperti untuk membagikan atau mentransfer kepemilikan."

Jon
sumber
8
Temuan bagus! Sangat menyenangkan melihat dua ahli terkemuka di bidang ini secara terbuka menyangkal kebijaksanaan konvensional pada SO.
Stephan Tolksdorf
3
"Tidak ada yang istimewa tentang shared_ptr dalam hal apakah Anda meneruskannya dengan nilai, atau meneruskannya dengan referensi" - Saya sangat tidak setuju dengan ini. Itu istimewa. Secara pribadi saya lebih suka bermain aman, dan menerima sedikit kinerja. Jika ada area kode tertentu yang perlu saya optimalkan, tentu saja, saya akan melihat manfaat kinerja dari shared_ptr pass by const ref.
JasonZ
3
Menarik juga untuk dicatat bahwa, meskipun ada kesepakatan tentang penggunaan yang berlebihan shared_ptr, tidak ada kesepakatan tentang masalah pass-by-value-vs.-reference.
Nicol Bolas
2
Scott Meyers: "jadi jika saya bisa lolos dengan semantik yang tepat dalam program saya ..." yaitu tidak bertentangan sama sekali dengan jawaban saya, yang menunjukkan bahwa mencari tahu apakah mengubah parameter const &akan memengaruhi semantik hanya mudah dilakukan program sederhana.
Daniel Earwicker
7
Herb Sutter: "kadang-kadang mungkin karena Anda tahu apa yang Anda sebut mungkin mengubah hal yang Anda dapatkan referensi dari". Sekali lagi, pengecualian kecil itu untuk kasus kecil, jadi tidak bertentangan dengan jawaban saya. Pertanyaannya tetap: bagaimana Anda tahu aman menggunakan ref const? Sangat mudah dibuktikan dalam program yang sederhana, tidak begitu mudah dalam program yang kompleks. Tapi hei, ini adalah C ++, dan jadi kami mendukung dini mikro-optimasi lebih hampir semua kekhawatiran teknik lainnya, kan ?! :)
Daniel Earwicker
22

Saya akan menyarankan agar praktik ini tidak dilakukan kecuali Anda dan programmer lain yang bekerja dengan Anda benar-benar, benar tahu apa yang Anda lakukan.

Pertama, Anda tidak tahu bagaimana antarmuka ke kelas Anda dapat berkembang dan Anda ingin mencegah programmer lain melakukan hal-hal buruk. Meneruskan shared_ptr dengan referensi bukanlah sesuatu yang diharapkan oleh programmer, karena itu tidak idiomatis, dan itu membuatnya mudah untuk digunakan secara tidak benar. Program defensif: membuat antarmuka sulit digunakan secara tidak benar. Melewati referensi hanya akan mengundang masalah di kemudian hari.

Kedua, jangan optimalkan sampai Anda tahu kelas khusus ini akan menjadi masalah. Profil dulu, dan kemudian jika program Anda benar-benar membutuhkan dorongan yang diberikan melalui referensi, maka mungkin. Jika tidak, jangan khawatirkan hal-hal kecil (yaitu instruksi N tambahan yang diperlukan untuk meneruskan nilai) alih-alih khawatir tentang desain, struktur data, algoritme, dan pemeliharaan jangka panjang.

Mark Kegel
sumber
Meskipun jawaban litb secara teknis benar, jangan pernah meremehkan "kemalasan" programmer (saya juga malas!). Jawaban littlenag lebih baik, bahwa referensi ke shared_ptr tidak terduga, dan mungkin (mungkin) pengoptimalan yang tidak perlu yang membuat pemeliharaan di masa mendatang lebih menantang.
netjeff
18

Ya, mengambil referensi tidak masalah di sana. Anda tidak bermaksud memberikan metode kepemilikan bersama; ia hanya ingin bekerja dengannya. Anda juga dapat mengambil referensi untuk kasus pertama, karena Anda tetap menyalinnya. Tapi untuk kasus pertama, ini membutuhkan kepemilikan. Ada trik untuk tetap menyalinnya hanya sekali:

void ClassA::take_copy_of_sp(boost::shared_ptr<foo> sp) {
    m_sp_member.swap(sp);
}

Anda juga harus menyalin saat mengembalikannya (yaitu tidak mengembalikan referensi). Karena kelas Anda tidak tahu apa yang dilakukan klien dengannya (kelas dapat menyimpan penunjuk ke sana dan kemudian terjadi ledakan besar). Jika nanti ternyata itu bottleneck (profil pertama!), Maka Anda masih bisa mengembalikan referensi.


Sunting : Tentu saja, seperti yang ditunjukkan orang lain, ini hanya berlaku jika Anda tahu kode Anda dan tahu bahwa Anda tidak mengatur ulang penunjuk bersama yang diberikan dengan cara tertentu. Jika ragu, berikan nilai saja.

Johannes Schaub - litb
sumber
11

Hal ini masuk akal untuk lulus shared_ptrs oleh const&. Ini sepertinya tidak akan menimbulkan masalah (kecuali dalam kasus yang tidak biasa bahwa referensi shared_ptrdihapus selama pemanggilan fungsi, seperti yang dijelaskan oleh Earwicker) dan kemungkinan akan lebih cepat jika Anda melewatkan banyak hal ini. Ingat; defaultnya boost::shared_ptradalah thread safe, jadi menyalinnya akan menyertakan penambahan thread safe.

Cobalah untuk menggunakan const&daripada hanya &, karena objek sementara mungkin tidak diteruskan oleh referensi non-const. (Meskipun ekstensi bahasa di MSVC memungkinkan Anda untuk tetap melakukannya)

Magnus Hoff
sumber
3
Ya, saya selalu menggunakan referensi const, saya lupa memasukkannya ke dalam contoh saya. Bagaimanapun, MSVC memungkinkan untuk mengikat referensi non-const ke temporaries bukan untuk bug, tetapi karena secara default memiliki properti "C / C ++ -> Bahasa -> Nonaktifkan Ekstensi Bahasa" disetel ke "NO". Aktifkan dan tidak akan mengkompilasinya ...
abigagli
abigagli: Serius? Manis! Saya akan memberlakukan ini di tempat kerja, hal pertama besok;)
Magnus Hoff
10

Dalam kasus kedua, melakukan ini lebih sederhana:

Class::only_work_with_sp(foo &sp)
{    
    ...  
    sp.do_something();  
    ...  
}

Anda bisa menyebutnya sebagai

only_work_with_sp(*sp);
Greg Rogers
sumber
3
Jika Anda mengadopsi konvensi untuk menggunakan referensi objek saat Anda tidak perlu mengambil salinan pointer, ini berfungsi untuk mendokumentasikan maksud Anda juga. Ini juga memberi Anda kesempatan untuk menggunakan referensi const.
Markus Tebusan
Ya, saya setuju penggunaan referensi ke objek sebagai sarana untuk menyatakan bahwa fungsi yang dipanggil tidak "mengingat" apa pun tentang objek itu. Biasanya saya menggunakan argumen formal pointer jika fungsinya adalah "melacak" objek
abigagli
3

Saya akan menghindari referensi "biasa" kecuali fungsi secara eksplisit dapat mengubah pointer.

A const &mungkin merupakan pengoptimalan mikro yang masuk akal saat memanggil fungsi kecil - misalnya untuk mengaktifkan pengoptimalan lebih lanjut, seperti meniadakan beberapa kondisi. Selain itu, increment / decrement - karena threadnya aman - adalah titik sinkronisasi. Namun, saya tidak berharap ini membuat perbedaan besar di sebagian besar skenario.

Umumnya, Anda harus menggunakan gaya yang lebih sederhana kecuali Anda punya alasan untuk tidak melakukannya. Kemudian, gunakan const &secara konsisten, atau tambahkan komentar tentang mengapa jika Anda menggunakannya hanya di beberapa tempat.

peterchen
sumber
3

Saya akan menganjurkan melewatkan pointer bersama dengan referensi const - semantik bahwa fungsi yang diteruskan dengan pointer TIDAK memiliki pointer, yang merupakan idiom bersih untuk pengembang.

Satu-satunya kesalahan adalah dalam beberapa program utas, objek yang diarahkan oleh penunjuk bersama akan dihancurkan di utas lain. Jadi aman untuk mengatakan menggunakan referensi const dari pointer bersama aman dalam program utas tunggal.

Meneruskan penunjuk bersama oleh referensi non-const terkadang berbahaya - alasannya adalah fungsi swap dan reset yang mungkin dipanggil oleh fungsi di dalam untuk menghancurkan objek yang masih dianggap valid setelah fungsi tersebut kembali.

Ini bukan tentang pengoptimalan prematur, saya kira - ini tentang menghindari pemborosan siklus CPU yang tidak perlu ketika Anda jelas apa yang ingin Anda lakukan dan idiom pengkodean telah diadopsi dengan kuat oleh sesama pengembang Anda.

Hanya 2 sen saya :-)

zmqiu
sumber
1
Lihat komentar di atas oleh David Schwartz "... Saya mengejar bug yang sangat serius yang terjadi karena melewatkan referensi ke penunjuk bersama. Kode tersebut menangani perubahan status objek dan ketika ia menyadari status objek telah berubah, itu menghapusnya dari koleksi objek di status sebelumnya dan memindahkannya ke koleksi objek di status baru. Operasi penghapusan menghancurkan penunjuk bersama terakhir ke objek. Fungsi anggota telah dipanggil pada referensi ke penunjuk bersama dalam koleksi. Boom ... "
Jason Harrison
3

Tampaknya semua pro dan kontra di sini sebenarnya dapat digeneralisasikan ke jenis APA PUN yang diteruskan dengan referensi bukan hanya shared_ptr. Menurut pendapat saya, Anda harus tahu semantik lewat referensi, referensi konstanta dan nilai dan menggunakannya dengan benar. Tetapi sama sekali tidak ada yang salah dengan meneruskan shared_ptr dengan referensi, kecuali Anda berpikir bahwa semua referensi itu buruk ...

Untuk kembali ke contoh:

Class::only_work_with_sp( foo &sp ) //Again, no copy here  
{    
    ...  
    sp.do_something();  
    ...  
}

Bagaimana Anda tahu bahwa sp.do_something() itu tidak akan meledak karena penunjuk yang menjuntai?

Yang benar adalah, shared_ptr atau tidak, const atau tidak, ini bisa terjadi jika Anda memiliki cacat desain, seperti berbagi kepemilikan secara langsung atau tidak langsung di spantara utas, melewatkan penggunaan objek yang melakukannya delete this, Anda memiliki kepemilikan melingkar atau kesalahan kepemilikan lainnya.

Sandy
sumber
2

Satu hal yang belum saya lihat disebutkan adalah bahwa ketika Anda melewati pointer bersama dengan referensi, Anda kehilangan konversi implisit yang Anda dapatkan jika Anda ingin meneruskan pointer bersama kelas turunan melalui referensi ke pointer bersama kelas dasar.

Misalnya, kode ini akan menghasilkan kesalahan, tetapi akan berfungsi jika Anda mengubah test()sehingga penunjuk bersama tidak diteruskan oleh referensi.

#include <boost/shared_ptr.hpp>

class Base { };
class Derived: public Base { };

// ONLY instances of Base can be passed by reference.  If you have a shared_ptr
// to a derived type, you have to cast it manually.  If you remove the reference
// and pass the shared_ptr by value, then the cast is implicit so you don't have
// to worry about it.
void test(boost::shared_ptr<Base>& b)
{
    return;
}

int main(void)
{
    boost::shared_ptr<Derived> d(new Derived);
    test(d);

    // If you want the above call to work with references, you will have to manually cast
    // pointers like this, EVERY time you call the function.  Since you are creating a new
    // shared pointer, you lose the benefit of passing by reference.
    boost::shared_ptr<Base> b = boost::dynamic_pointer_cast<Base>(d);
    test(b);

    return 0;
}
Malvineous
sumber
1

Saya akan berasumsi bahwa Anda sudah familiar dengan pengoptimalan prematur dan menanyakan ini baik untuk tujuan akademis atau karena Anda telah mengisolasi beberapa kode yang sudah ada sebelumnya yang berkinerja buruk.

Melewati referensi tidak apa-apa

Melewati referensi const lebih baik, dan biasanya dapat digunakan, karena tidak memaksa konstanta pada objek yang dituju.

Anda tidak berisiko kehilangan penunjuk karena menggunakan referensi. Referensi tersebut adalah bukti bahwa Anda memiliki salinan penunjuk cerdas sebelumnya dalam tumpukan dan hanya satu utas yang memiliki tumpukan panggilan, sehingga salinan yang sudah ada tidak akan hilang.

Menggunakan referensi seringkali lebih efisien untuk alasan yang Anda sebutkan, tetapi tidak dijamin . Ingatlah bahwa dereferensi suatu objek juga membutuhkan pekerjaan. Skenario penggunaan referensi ideal Anda adalah jika gaya pengkodean Anda melibatkan banyak fungsi kecil, di mana penunjuk akan diteruskan dari fungsi ke fungsi ke fungsi sebelum digunakan.

Anda harus selalu menghindari menyimpan penunjuk cerdas Anda sebagai referensi. Class::take_copy_of_sp(&sp)Contoh Anda menunjukkan penggunaan yang benar untuk itu.

Drew Dormann
sumber
1
"Anda tidak berisiko kehilangan pointer karena menggunakan referensi. Referensi itu adalah bukti bahwa Anda memiliki salinan pointer pintar sebelumnya di tumpukan" Atau anggota data ...?
Daniel Earwicker
Pertimbangkan keajaiban boost :: thread dan boost :: ref: boost :: function <int> functionPointer = boost :: bind (doSomething, boost :: ref (sharedPtrInstance)); m_workerThread = dorongan baru :: utas (functionPointer); ... hapus sharedPtrInstance
Jason Harrison
1

Dengan asumsi kita tidak peduli dengan kebenaran const (atau lebih, Anda bermaksud mengizinkan fungsi untuk dapat mengubah atau berbagi kepemilikan data yang sedang diteruskan), meneruskan boost :: shared_ptr by value lebih aman daripada meneruskannya dengan referensi sebagai kami mengizinkan boost :: shared_ptr asli untuk mengontrol masa pakainya sendiri. Perhatikan hasil dari kode berikut ...

void FooTakesReference( boost::shared_ptr< int > & ptr )
{
    ptr.reset(); // We reset, and so does sharedA, memory is deleted.
}

void FooTakesValue( boost::shared_ptr< int > ptr )
{
    ptr.reset(); // Our temporary is reset, however sharedB hasn't.
}

void main()
{
    boost::shared_ptr< int > sharedA( new int( 13 ) );
    boost::shared_ptr< int > sharedB( new int( 14 ) );

    FooTakesReference( sharedA );

    FooTakesValue( sharedB );
}

Dari contoh di atas, kita melihat bahwa meneruskan sharedA dengan referensi memungkinkan FooTakesReference menyetel ulang penunjuk asli, yang mengurangi jumlah penggunaannya menjadi 0, menghancurkan datanya. FooTakesValue , bagaimanapun, tidak dapat mengatur ulang penunjuk asli, menjamin data sharedB masih dapat digunakan. Ketika pengembang lain datang dan mencoba untuk mendukung keberadaan sharedA yang rapuh, kekacauan pun terjadi. Pengembang sharedB yang beruntung , bagaimanapun, pulang lebih awal karena semuanya baik-baik saja di dunianya.

Keamanan kode, dalam hal ini, jauh melebihi peningkatan kecepatan yang diciptakan penyalinan. Pada saat yang sama, boost :: shared_ptr dimaksudkan untuk meningkatkan keamanan kode. Akan jauh lebih mudah untuk beralih dari salinan ke referensi, jika sesuatu memerlukan pengoptimalan niche semacam ini.

Kit10
sumber
1

Sandy menulis: "Tampaknya semua pro dan kontra di sini sebenarnya dapat digeneralisasikan ke jenis APA PUN yang diteruskan dengan referensi bukan hanya shared_ptr."

Benar sampai batas tertentu, tetapi tujuan menggunakan shared_ptr adalah untuk menghilangkan kekhawatiran tentang masa pakai objek dan membiarkan compiler menanganinya untuk Anda. Jika Anda akan meneruskan penunjuk bersama dengan referensi dan mengizinkan klien metode non-const panggilan referensi-objek-dihitung yang mungkin membebaskan data objek, maka penggunaan penunjuk bersama hampir tidak ada gunanya.

Saya menulis "hampir" dalam kalimat sebelumnya karena kinerja dapat menjadi perhatian, dan itu 'mungkin' dibenarkan dalam kasus yang jarang terjadi, tetapi saya juga akan menghindari skenario ini sendiri dan mencari sendiri semua kemungkinan solusi pengoptimalan lainnya, seperti untuk melihat dengan serius dalam menambahkan tingkat tipuan lain, evaluasi malas, dll.

Kode yang ada setelah penulisnya, atau bahkan memposting memori penulisnya, yang memerlukan asumsi implisit tentang perilaku, khususnya perilaku tentang masa pakai objek, memerlukan dokumentasi yang jelas, ringkas, dapat dibaca, dan kemudian banyak klien tidak akan membacanya! Kesederhanaan hampir selalu mengalahkan efisiensi, dan hampir selalu ada cara lain untuk menjadi efisien. Jika Anda benar-benar perlu meneruskan nilai dengan referensi untuk menghindari penyalinan mendalam oleh konstruktor salinan objek yang dihitung-referensi Anda (dan operator yang sama), maka mungkin Anda harus mempertimbangkan cara untuk membuat data yang disalin dalam menjadi referensi terhitung petunjuk yang dapat digunakan. disalin dengan cepat. (Tentu saja, itu hanya satu skenario desain yang mungkin tidak berlaku untuk situasi Anda).

Bill H.
sumber
1

Saya dulu bekerja dalam sebuah proyek yang prinsipnya sangat kuat tentang menyampaikan petunjuk cerdas dengan nilai. Ketika saya diminta untuk melakukan beberapa analisis kinerja - saya menemukan bahwa untuk kenaikan dan penurunan penghitung referensi dari pointer pintar, aplikasi menghabiskan antara 4-6% dari waktu prosesor yang digunakan.

Jika Anda ingin meneruskan petunjuk cerdas dengan nilai hanya untuk menghindari masalah dalam kasus aneh seperti yang dijelaskan dari Daniel Earwicker, pastikan Anda memahami harga yang Anda bayar untuk itu.

Jika Anda memutuskan untuk menggunakan referensi, alasan utama untuk menggunakan referensi const adalah untuk memungkinkan adanya upcasting implisit saat Anda perlu meneruskan pointer bersama ke objek dari kelas yang mewarisi kelas yang Anda gunakan dalam antarmuka.

gsf
sumber
0

Sebagai tambahan dari apa yang litb katakan, saya ingin menunjukkan bahwa itu mungkin melewati referensi const pada contoh kedua, dengan cara itu Anda yakin Anda tidak sengaja memodifikasinya.

Leon Timmermans
sumber
0
struct A {
  shared_ptr<Message> msg;
  shared_ptr<Message> * ptr_msg;
}
  1. melewati nilai:

    void set(shared_ptr<Message> msg) {
      this->msg = msg; /// create a new shared_ptr, reference count will be added;
    } /// out of method, new created shared_ptr will be deleted, of course, reference count also be reduced;
  2. melewati referensi:

    void set(shared_ptr<Message>& msg) {
     this->msg = msg; /// reference count will be added, because reference is just an alias.
     }
  3. melewati penunjuk:

    void set(shared_ptr<Message>* msg) {
      this->ptr_msg = msg; /// reference count will not be added;
    }
Dylan Chen
sumber
0

Setiap bagian kode harus memiliki arti. Jika Anda memberikan penunjuk bersama berdasarkan nilai di mana pun dalam aplikasi, ini berarti "Saya tidak yakin tentang apa yang terjadi di tempat lain, oleh karena itu saya lebih menyukai keamanan mentah ". Ini bukanlah apa yang saya sebut sebagai tanda kepercayaan diri yang baik bagi pemrogram lain yang dapat melihat kode tersebut.

Bagaimanapun, meskipun suatu fungsi mendapatkan referensi konstanta dan Anda "tidak yakin", Anda masih dapat membuat salinan penunjuk bersama di bagian atas fungsi, untuk menambahkan referensi yang kuat ke penunjuk. Ini juga bisa dilihat sebagai petunjuk tentang desain ("penunjuk dapat dimodifikasi di tempat lain").

Jadi ya, IMO, defaultnya harus " pass by const reference ".

Philippe
sumber