Saat fungsi mengambil shared_ptr
(dari boost atau C ++ 11 STL), apakah Anda mengopernya:
dengan referensi const:
void foo(const shared_ptr<T>& p)
atau berdasarkan nilai
void foo(shared_ptr<T> p)
:?
Saya lebih suka metode pertama karena saya curiga akan lebih cepat. Tetapi apakah ini benar-benar layak atau adakah masalah tambahan?
Bisakah Anda memberikan alasan untuk pilihan Anda atau jika kasusnya, mengapa Anda pikir itu tidak masalah.
c++
c++11
boost
shared-ptr
Danvil
sumber
sumber
shared_ptr
, dan saya bisa mengubahnya jika saya mau.", Sementara versi nilai mengatakan "Saya akan menyalin Andashared_ptr
, jadi sementara saya bisa mengubahnya, Anda tidak akan pernah tahu. ) Parameter const-reference adalah solusi nyata, yang mengatakan "Saya akan alias beberapashared_ptr
, dan saya berjanji untuk tidak mengubahnya." (Yang sangat mirip dengan nilai-semantik!)shared_ptr
kelas. Apakah Anda melakukannya dengan const-ref?Jawaban:
Pertanyaan ini telah didiskusikan dan dijawab oleh Scott, Andrei dan Herb selama sesi Ask Us Anything di C ++ and Beyond 2011 . Tonton mulai 4:34 tentang
shared_ptr
kinerja dan kebenaran .Singkatnya, tidak ada alasan untuk memberikan nilai, kecuali tujuannya adalah untuk berbagi kepemilikan atas suatu objek (mis. Antara struktur data yang berbeda, atau antara utas yang berbeda).
Kecuali Anda dapat menggerakkan-mengoptimalkannya seperti yang dijelaskan oleh Scott Meyers dalam video pembicaraan yang ditautkan di atas, tetapi itu terkait dengan versi aktual C ++ yang dapat Anda gunakan.
Pembaruan besar untuk diskusi ini telah terjadi selama Panel Interaktif konferensi GoingNative 2012 : Tanyakan Apa Pun ! yang layak ditonton, terutama dari 22:50 .
sumber
Value*
pendek dan mudah dibaca, tetapi buruk, jadi sekarang kode saya penuhconst shared_ptr<Value>&
dan secara signifikan lebih mudah dibaca dan hanya ... kurang rapi. Apa yang duluvoid Function(Value* v1, Value* v2, Value* v3)
sekarangvoid Function(const shared_ptr<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3)
, dan orang-orang baik-baik saja dengan ini?class Value {...}; using ValuePtr = std::shared_ptr<Value>;
Maka fungsi Anda menjadi lebih sederhana:void Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3)
dan Anda mendapatkan kinerja maksimal. Itu sebabnya Anda menggunakan C ++, bukan? :)Ini ramuan Herb Sutter
sumber
Secara pribadi saya akan menggunakan
const
referensi. Tidak perlu menambah jumlah referensi hanya untuk mengurangi lagi demi pemanggilan fungsi.sumber
shared_ptr
kerjanya, satu-satunya downside yang mungkin terjadi jika tidak melewati referensi adalah sedikit penurunan kinerja. Ada dua penyebab di sini. a) fitur pointer aliasing berarti pointer bernilai data plus penghitung (mungkin 2 untuk referensi lemah) disalin, sehingga sedikit lebih mahal untuk menyalin ronde data. b) penghitungan referensi atom sedikit lebih lambat dari kode kenaikan / penurunan tua biasa, tetapi diperlukan agar benang aman. Di luar itu, kedua metode itu sama untuk sebagian besar maksud dan tujuan.Lulus dengan
const
referensi, lebih cepat. Jika Anda perlu menyimpannya, katakan dalam beberapa wadah, ref. hitungan akan bertambah secara ajaib oleh operasi penyalinan.sumber
Aku berlari kode di bawah ini, sekali dengan
foo
mengambilshared_ptr
olehconst&
dan lagi denganfoo
mengambilshared_ptr
berdasarkan nilai.Menggunakan VS2015, build rilis x86, pada prosesor intel core 2 quad (2.4GHz) saya
Salinan menurut versi nilai adalah urutan besarnya lebih lambat.
Jika Anda memanggil fungsi secara sinkron dari utas saat ini, lebih suka
const&
versinya.sumber
foo()
Fungsi seperti itu seharusnya bahkan tidak menerima pointer bersama di tempat pertama karena itu tidak menggunakan objek ini: itu harus menerimaint&
dan lakukanp = ++x;
, memanggilfoo(*p);
darimain()
. Fungsi menerima objek penunjuk pintar ketika perlu melakukan sesuatu dengannya, dan sebagian besar waktu, yang perlu Anda lakukan adalah memindahkannya (std::move()
) ke tempat lain, sehingga parameter nilai-per-satuan tidak memerlukan biaya.Karena C ++ 11 Anda harus mengambilnya dengan nilai lebih dari const & lebih sering daripada yang Anda pikirkan.
Jika Anda menggunakan std :: shared_ptr (daripada tipe T yang mendasarinya), maka Anda melakukannya karena Anda ingin melakukan sesuatu dengannya.
Jika Anda ingin menyalinnya suatu tempat, lebih masuk akal untuk mengambilnya dengan menyalin, dan std :: memindahkannya secara internal, daripada mengambilnya dengan const & dan kemudian menyalinnya. Ini karena Anda membiarkan opsi penelepon pada gilirannya std :: memindahkan shared_ptr saat memanggil fungsi Anda, sehingga menghemat sendiri satu set operasi kenaikan dan penurunan. Atau tidak. Artinya, pemanggil fungsi dapat memutuskan apakah dia memerlukan std :: shared_ptr di sekitar setelah memanggil fungsi, dan tergantung pada apakah atau tidak bergerak atau tidak. Ini tidak dapat dicapai jika Anda melewati const &, dan karena itu lebih disukai untuk mengambilnya dengan nilai.
Tentu saja, jika pemanggil keduanya membutuhkan shared_ptr-nya lebih lama (sehingga tidak dapat std :: memindahkannya) dan Anda tidak ingin membuat salinan polos dalam fungsi (misalkan Anda ingin penunjuk yang lemah, atau Anda hanya kadang-kadang ingin untuk menyalinnya, tergantung pada beberapa kondisi), maka konst & mungkin masih lebih disukai.
Misalnya, Anda harus melakukannya
lebih
Karena dalam hal ini Anda selalu membuat salinan secara internal
sumber
Tidak tahu biaya waktu operasi copy shared_copy di mana peningkatan dan penurunan atom, saya menderita masalah penggunaan CPU yang jauh lebih tinggi. Saya tidak pernah menyangka peningkatan dan penurunan atom mungkin memakan banyak biaya.
Setelah hasil pengujian saya, kenaikan dan penurunan atom int32 membutuhkan 2 atau 40 kali lipat daripada kenaikan dan penurunan non-atom. Saya mendapatkannya di 3GHz Core i7 dengan Windows 8.1. Hasil pertama muncul ketika tidak ada pertengkaran yang terjadi, yang terakhir ketika kemungkinan pertengkaran tinggi terjadi. Saya ingat bahwa operasi atom pada kunci berbasis perangkat keras yang terakhir. Kunci adalah kunci. Buruk terhadap kinerja saat pertikaian terjadi.
Mengalami ini, saya selalu menggunakan byref (const shared_ptr &) daripada byval (shared_ptr).
sumber
Ada posting blog baru-baru ini: https://medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce
Jadi jawabannya adalah: Jangan (hampir) tidak pernah lewat
const shared_ptr<T>&
.Cukup lulus kelas yang mendasarinya saja.
Pada dasarnya satu-satunya jenis parameter yang masuk akal adalah:
shared_ptr<T>
- Ubah dan ambil kepemilikanshared_ptr<const T>
- Jangan modifikasi, ambil kepemilikanT&
- Ubah, tidak ada kepemilikanconst T&
- Jangan modifikasi, tidak ada kepemilikanT
- Jangan modifikasi, tanpa kepemilikan, Murah untuk menyalinSeperti @accel tunjukkan dalam https://stackoverflow.com/a/26197326/1930508 saran dari Herb Sutter adalah:
Tetapi dalam berapa kasus Anda tidak yakin? Jadi ini adalah situasi yang jarang terjadi
sumber
Sudah diketahui masalah bahwa meneruskan shared_ptr menurut nilai memiliki biaya dan harus dihindari jika mungkin.
Biaya melewati dengan shared_ptr
Sebagian besar waktu lewat shared_ptr dengan referensi, dan bahkan lebih baik dengan referensi const, akan dilakukan.
Pedoman inti cpp memiliki aturan khusus untuk meneruskan shared_ptr
R.34: Ambil parameter shared_ptr untuk menyatakan bahwa suatu fungsi adalah pemilik bagian
Contoh kapan lewat shared_ptr menurut nilai sangat diperlukan adalah ketika pemanggil meneruskan objek yang dibagi ke callee asinkron - yaitu pemanggil keluar dari ruang lingkup sebelum callee menyelesaikan tugasnya. Callee harus "memperpanjang" masa pakai objek yang dibagikan dengan mengambil nilai share_ptr. Dalam hal ini, meneruskan referensi ke shared_ptr tidak akan berhasil.
Hal yang sama berlaku untuk melewatkan objek bersama ke utas kerja.
sumber
shared_ptr tidak cukup besar, konstruktor \ destruktornya juga tidak cukup bekerja agar ada overhead yang cukup dari salinan untuk peduli dengan referensi demi referensi vs demi kinerja copy.
sumber
shared_ptr<int>
nilai berdasarkan membutuhkan lebih dari 100 instruksi x86 (termasuklock
instruksi ed mahal untuk secara atom memasukkan / memutuskan jumlah referensi). Melewati oleh ref konstan sama dengan melewatkan pointer ke apa saja (dan dalam contoh ini pada explorer compiler Godbolt, optimasi tail-call mengubahnya menjadi jmp sederhana alih-alih panggilan: godbolt.org/g/TazMBU ).