Apakah lebih baik di C ++ untuk melewati nilai atau lulus dengan referensi konstan?
Saya bertanya-tanya latihan mana yang lebih baik. Saya menyadari bahwa referensi konstan harus memberikan kinerja yang lebih baik dalam program ini karena Anda tidak membuat salinan variabel.
c++
variables
pass-by-reference
constants
pass-by-value
Matt Pascoe
sumber
sumber
Jawaban:
Dulu umumnya direkomendasikan praktek terbaik 1 untuk penggunaan lewat ref const untuk semua jenis , kecuali untuk builtin jenis (
char
,int
,double
, dll), untuk iterator dan untuk benda fungsi (lambdas, kelas berasal daristd::*_function
).Ini terutama benar sebelum keberadaan semantik bergerak . Alasannya sederhana: jika Anda melewati nilai, salinan objek harus dibuat dan, kecuali untuk objek yang sangat kecil, ini selalu lebih mahal daripada melewati referensi.
Dengan C ++ 11, kami telah mendapatkan semantik bergerak . Singkatnya, memindahkan semantik memungkinkan bahwa, dalam beberapa kasus, suatu objek dapat dilewati "dengan nilai" tanpa menyalinnya. Secara khusus, hal ini terjadi ketika objek yang Anda melewati sebuah nilai p .
Dalam dirinya sendiri, memindahkan objek masih setidaknya semahal melewati referensi. Namun, dalam banyak kasus suatu fungsi akan secara internal menyalin objek - yaitu akan mengambil kepemilikan dari argumen. 2
Dalam situasi ini kami memiliki pertukaran (disederhanakan) sebagai berikut:
"Pass by value" masih menyebabkan objek disalin, kecuali jika objek tersebut adalah nilai. Dalam hal nilai, objek dapat dipindahkan sebagai gantinya, sehingga kasus kedua tiba-tiba tidak lagi "menyalin, lalu memindahkan" tetapi "bergerak, lalu (berpotensi) bergerak lagi".
Untuk objek besar yang menerapkan konstruktor pemindahan yang tepat (seperti vektor, string ...), case kedua jauh lebih efisien daripada yang pertama. Oleh karena itu, disarankan untuk menggunakan pass by value jika fungsi tersebut mengambil kepemilikan dari argumen, dan jika jenis objek mendukung perpindahan efisien .
Catatan sejarah:
Bahkan, setiap kompiler modern harus dapat mencari tahu ketika melewati nilai mahal, dan secara implisit mengkonversi panggilan untuk menggunakan referensi ref jika memungkinkan.
Dalam teori. Dalam praktiknya, kompiler tidak selalu dapat mengubah ini tanpa merusak antarmuka biner fungsi. Dalam beberapa kasus khusus (ketika fungsi ini digarisbawahi) salinan akan benar-benar hilang jika kompiler dapat mengetahui bahwa objek asli tidak akan diubah melalui tindakan dalam fungsi.
Tetapi secara umum kompiler tidak dapat menentukan ini, dan munculnya semantik bergerak di C ++ telah membuat optimasi ini jauh kurang relevan.
1 Misalnya dalam Scott Meyers, Efektif C ++ .
2 Ini sering benar untuk konstruktor objek, yang dapat mengambil argumen dan menyimpannya secara internal untuk menjadi bagian dari keadaan objek yang dikonstruksi.
sumber
Sunting: Artikel baru oleh Dave Abrahams di cpp-next:
Ingin kecepatan? Lewati dengan nilai.
Pass by value untuk struct di mana penyalinannya murah memiliki keuntungan tambahan bahwa kompiler dapat mengasumsikan bahwa objek tidak alias (bukan objek yang sama). Menggunakan pass-by-reference, kompiler tidak dapat menganggap itu selalu. Contoh sederhana:
kompiler dapat mengoptimalkannya menjadi
karena ia tahu bahwa f dan g tidak berbagi lokasi yang sama. jika g adalah referensi (foo &), kompiler tidak dapat menganggap itu. karena gi kemudian dapat di-alias oleh f-> i dan harus memiliki nilai 7. sehingga compiler harus mengambil kembali nilai baru gi dari memori.
Untuk aturan yang lebih praktis, berikut adalah seperangkat aturan yang baik yang ditemukan di artikel Pindahkan Konstruktor (bacaan yang sangat disarankan).
"Primitif" di atas pada dasarnya berarti tipe data kecil yang panjangnya beberapa byte dan tidak polimorfik (iterator, objek fungsi, dll ...) atau mahal untuk disalin. Dalam makalah itu, ada satu aturan lain. Idenya adalah bahwa kadang-kadang seseorang ingin membuat salinan (dalam kasus argumen tidak dapat dimodifikasi), dan kadang-kadang seseorang tidak mau (jika seseorang ingin menggunakan argumen itu sendiri dalam fungsi jika argumen itu bersifat sementara saja , sebagai contoh). Makalah ini menjelaskan secara rinci bagaimana hal itu dapat dilakukan. Dalam C ++ 1x teknik itu dapat digunakan secara native dengan dukungan bahasa. Sampai saat itu, saya akan pergi dengan aturan di atas.
Contoh: Untuk membuat string huruf besar dan mengembalikan versi huruf besar, kita harus selalu memberikan nilai: Kita tetap harus mengambil salinannya (kita tidak bisa mengubah referensi const secara langsung) - jadi lebih baik membuatnya menjadi setransparan mungkin untuk penelepon dan buat salinan itu lebih awal sehingga penelepon dapat mengoptimalkan sebanyak mungkin - sebagaimana dirinci dalam makalah itu:
Namun, jika Anda tidak perlu mengubah parameternya, bawa dengan referensi ke const:
Namun, jika Anda tujuan dari parameter adalah untuk menulis sesuatu ke dalam argumen, maka berikan referensi non-const
sumber
__restrict__
(yang juga bisa bekerja pada referensi) daripada melakukan salinan berlebihan. Sayang sekali standar C ++ belum mengadopsirestrict
kata kunci C99 .Tergantung pada jenisnya. Anda menambahkan overhead kecil karena harus membuat referensi dan referensi. Untuk jenis dengan ukuran yang sama atau lebih kecil dari pointer yang menggunakan copy ctor default, mungkin akan lebih cepat untuk melewati nilai.
sumber
Seperti yang telah ditunjukkan, itu tergantung pada jenisnya. Untuk tipe data bawaan, yang terbaik adalah memberikan nilai. Bahkan beberapa struktur yang sangat kecil, seperti sepasang int dapat bekerja lebih baik dengan melewati nilai.
Berikut ini sebuah contoh, anggap Anda memiliki nilai integer dan Anda ingin meneruskannya ke rutin lain. Jika nilai itu telah dioptimalkan untuk disimpan dalam register, maka jika Anda ingin meneruskannya menjadi referensi, pertama-tama harus disimpan dalam memori dan kemudian sebuah penunjuk ke memori yang ditempatkan pada tumpukan untuk melakukan panggilan. Jika itu diteruskan oleh nilai, semua yang diperlukan adalah register didorong ke stack. (Detailnya sedikit lebih rumit daripada yang diberikan sistem panggilan dan CPU yang berbeda).
Jika Anda melakukan pemrograman templat, Anda biasanya dipaksa untuk selalu melewati const ref karena Anda tidak tahu jenis yang dilewati. Meneruskan hukuman karena melewatkan sesuatu yang buruk menurut nilainya jauh lebih buruk daripada hukuman lewat tipe bawaan oleh const ref.
sumber
Inilah yang biasanya saya kerjakan saat mendesain antarmuka fungsi non-templat:
Lewati nilai jika fungsi tidak ingin memodifikasi parameter dan nilainya murah untuk disalin (int, dobel, float, char, bool, dll. Perhatikan bahwa std :: string, std :: vector, dan yang lainnya kontainer di perpustakaan standar TIDAK)
Lewati oleh pointer pointer jika nilainya mahal untuk disalin dan fungsinya tidak ingin mengubah nilai yang ditunjukkan dan NULL adalah nilai yang ditangani oleh fungsi tersebut.
Lewati oleh pointer non-const jika nilainya mahal untuk disalin dan fungsi ingin memodifikasi nilai yang ditunjukkan dan NULL adalah nilai yang ditangani oleh fungsi tersebut.
Lewati referensi const ketika nilainya mahal untuk disalin dan fungsinya tidak ingin mengubah nilai yang dirujuk dan NULL tidak akan menjadi nilai yang valid jika pointer digunakan sebagai gantinya.
Lewati referensi non-const ketika nilainya mahal untuk disalin dan fungsi ingin memodifikasi nilai yang dirujuk dan NULL tidak akan menjadi nilai yang valid jika pointer digunakan sebagai gantinya.
sumber
std::optional
ke gambar dan Anda tidak perlu lagi pointer.Sepertinya Anda mendapat jawaban Anda. Melewati dengan nilai itu mahal, tetapi memberi Anda salinan untuk bekerja jika Anda membutuhkannya.
sumber
Sebagai aturan yang melewati referensi const lebih baik. Tetapi jika Anda perlu memodifikasi argumen fungsi Anda secara lokal, Anda sebaiknya menggunakan passing oleh nilai. Untuk beberapa tipe dasar, kinerja pada umumnya sama baik untuk melewati nilai dan referensi. Sebenarnya referensi yang diwakili secara internal oleh pointer, itulah sebabnya Anda dapat berharap misalnya bahwa untuk pointer keduanya lewat sama dalam hal kinerja, atau bahkan lewat nilai dapat lebih cepat karena dereference yang tidak perlu.
sumber
Sebagai patokan, nilai untuk tipe non-kelas dan referensi const untuk kelas. Jika sebuah kelas sangat kecil mungkin lebih baik untuk lulus dengan nilai, tetapi perbedaannya minimal. Apa yang Anda benar-benar ingin hindari adalah melewati beberapa kelas raksasa dengan nilai dan menduplikasi semuanya - ini akan membuat perbedaan besar jika Anda meneruskan, katakanlah, std :: vector dengan beberapa elemen di dalamnya.
sumber
std::vector
sebenarnya mengalokasikan item pada heap dan objek vektor itu sendiri tidak pernah tumbuh. Oh tunggu. Jika operasi menyebabkan salinan vektor dibuat, sebenarnya ia akan pergi dan menduplikasi semua elemen. Itu buruk.sizeof(std::vector<int>)
adalah konstan, tetapi meneruskannya dengan nilai masih akan menyalin konten tanpa adanya kepintaran kompiler.Lewati nilai untuk tipe kecil.
Lewati referensi const untuk tipe besar (definisi big dapat bervariasi di antara mesin) TETAPI, dalam C ++ 11, berikan nilai jika Anda akan mengkonsumsi data, karena Anda dapat mengeksploitasi semantik yang bergerak. Sebagai contoh:
Sekarang kode panggilan akan dilakukan:
Dan hanya satu objek yang akan dibuat dan dipindahkan langsung ke anggota
name_
di kelasPerson
. Jika Anda melewati referensi const, salinan harus dibuat untuk memasukkannya ke dalamname_
.sumber
Perbedaan sederhana: - Dalam fungsi kita memiliki parameter input dan output, jadi jika input dan parameter keluar yang lewat sama, maka gunakan panggilan dengan referensi lain jika input dan parameter output berbeda maka lebih baik menggunakan panggilan berdasarkan nilai.
contoh
void amount(int account , int deposit , int total )
parameter input: akun, parameter output setoran: total
input dan out berbeda penggunaan panggilan oleh vaule
void amount(int total , int deposit )
total input jumlah total deposit
sumber