Kembalikan petunjuk cerdas dengan nilai.
Seperti yang Anda katakan, jika Anda mengembalikannya dengan referensi, Anda tidak akan menaikkan jumlah referensi dengan benar, yang membuka risiko menghapus sesuatu pada waktu yang tidak tepat. Itu saja sudah cukup menjadi alasan untuk tidak kembali dengan referensi. Antarmuka harus kuat.
Masalah biaya saat ini diperdebatkan berkat pengoptimalan nilai pengembalian (RVO), jadi Anda tidak akan menimbulkan urutan kenaikan-kenaikan-penurunan atau sesuatu seperti itu di kompiler modern. Jadi cara terbaik untuk mengembalikan a shared_ptr
adalah dengan hanya mengembalikan dengan nilai:
shared_ptr<T> Foo()
{
return shared_ptr<T>(/* acquire something */);
};
Ini adalah peluang RVO yang sangat jelas untuk kompiler C ++ modern. Saya tahu pasti bahwa kompiler Visual C ++ menerapkan RVO bahkan ketika semua pengoptimalan dimatikan. Dan dengan semantik move C ++ 11, kekhawatiran ini menjadi kurang relevan. (Tetapi satu-satunya cara untuk memastikan adalah membuat profil dan bereksperimen.)
Jika Anda masih belum yakin, Dave Abrahams sudah yakin artikel yang membuat argumen untuk dikembalikan berdasarkan nilai. Saya mereproduksi potongan di sini; Saya sangat menyarankan Anda membaca seluruh artikel:
Jujurlah: bagaimana kode berikut memengaruhi perasaan Anda?
std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();
Terus terang, meskipun saya harus lebih tahu, itu membuat saya gugup. Pada prinsipnya, ketika get_names()
kembali, kita harus menyalin vector
daristring
s. Kemudian, kita perlu menyalinnya lagi saat kita menginisialisasi
names
, dan kita perlu menghancurkan salinan pertama. Jika ada N string
dalam vektor, setiap salinan dapat memerlukan alokasi memori N + 1 dan sejumlah besar akses data tidak ramah cache> saat konten string disalin.
Alih-alih menghadapi kecemasan semacam itu, saya sering mengabaikan referensi untuk menghindari salinan yang tidak perlu:
get_names(std::vector<std::string>& out_param );
...
std::vector<std::string> names;
get_names( names );
Sayangnya, pendekatan ini jauh dari ideal.
- Kode tumbuh 150%
- Kami harus turun
const
-ness karena kami mengubah nama.
- Seperti yang sering diingatkan oleh programmer fungsional kepada kita, mutasi membuat kode lebih kompleks untuk dipikirkan dengan merusak transparansi referensial dan penalaran persamaan.
- Kami tidak lagi memiliki semantik nilai ketat untuk nama.
Tetapi apakah benar-benar perlu mengacaukan kode kita dengan cara ini untuk mendapatkan efisiensi? Untungnya, jawabannya ternyata tidak (dan terutama tidak jika Anda menggunakan C ++ 0x).
cout << "Hello World!";
pernyataan dalam default dan copy konstruktor, Anda tidak akan melihat duaHello World!
saat RVO diterapkan. Namun, ini seharusnya tidak menjadi masalah untuk penunjuk cerdas yang dirancang dengan benar, bahkan sinkronisasi wrt.Mengenai apapun pointer pintar (bukan hanya shared_ptr), saya tidak berpikir itu pernah diterima kembali referensi ke salah satu, dan saya akan sangat ragu-ragu untuk melewati mereka sekitar dengan referensi atau pointer mentah. Mengapa? Karena Anda tidak dapat memastikan bahwa itu tidak akan disalin secara dangkal melalui referensi nanti. Poin pertama Anda menjelaskan alasan mengapa hal ini harus menjadi perhatian. Ini dapat terjadi bahkan di lingkungan single-threaded. Anda tidak memerlukan akses bersamaan ke data untuk menempatkan semantik salinan yang buruk di program Anda. Anda tidak benar-benar mengontrol apa yang dilakukan pengguna Anda dengan penunjuk setelah Anda memberikannya, jadi jangan mendorong penyalahgunaan untuk memberi pengguna API cukup tali untuk menggantung diri.
Kedua, lihat implementasi smart pointer Anda, jika memungkinkan. Konstruksi dan penghancuran harus hampir dapat diabaikan. Jika overhead ini tidak dapat diterima, maka jangan gunakan smart pointer! Namun di luar ini, Anda juga perlu memeriksa arsitektur konkurensi yang Anda miliki, karena akses yang saling eksklusif ke mekanisme yang melacak penggunaan penunjuk akan memperlambat Anda lebih dari sekadar konstruksi objek shared_ptr.
Sunting, 3 tahun kemudian: dengan munculnya fitur yang lebih modern di C ++, saya akan mengubah jawaban saya menjadi lebih menerima kasus ketika Anda hanya menulis lambda yang tidak pernah berada di luar ruang lingkup fungsi panggilan, dan tidak disalin di tempat lain. Di sini, jika Anda ingin menghemat overhead yang sangat minimal untuk menyalin penunjuk bersama, itu akan adil dan aman. Mengapa? Karena Anda dapat menjamin bahwa referensi tersebut tidak akan pernah disalahgunakan.
sumber