Seperti yang ditunjukkan oleh @ JDługosz dalam komentar, Herb memberikan saran lain dalam pembicaraan lain (nanti?), Lihat kira-kira dari sini: https://youtu.be/xnqTKD8uD64?t=54m50s .
Sarannya bermuara pada hanya menggunakan parameter nilai untuk fungsi f
yang mengambil apa yang disebut argumen wastafel, dengan asumsi Anda akan memindahkan konstruk dari argumen wastafel ini.
Pendekatan umum ini hanya menambahkan overhead dari konstruktor bergerak untuk argumen lvalue dan rvalue dibandingkan dengan implementasi optimal yang f
disesuaikan dengan argumen lvalue dan rvalue masing-masing. Untuk melihat mengapa hal ini terjadi, anggaplah f
mengambil parameter nilai, di mana T
ada beberapa salin dan pindahkan tipe konstruktif:
void f(T x) {
T y{std::move(x)};
}
Memanggil f
dengan argumen lvalue akan menghasilkan copy constructor yang dipanggil untuk membangun x
, dan move constructor dipanggil untuk membangun y
. Di sisi lain, memanggil f
dengan argumen nilai akan menyebabkan pemindah konstruktor dipanggil untuk membangun x
, dan pemindah pemindah lain dipanggil untuk membangun y
.
Secara umum, implementasi optimal f
untuk argumen lvalue adalah sebagai berikut:
void f(const T& x) {
T y{x};
}
Dalam hal ini, hanya satu copy constructor yang dipanggil untuk membangun y
. Implementasi optimal f
untuk argumen nilai, sekali lagi secara umum, sebagai berikut:
void f(T&& x) {
T y{std::move(x)};
}
Dalam hal ini, hanya satu konstruktor bergerak dipanggil untuk membangun y
.
Jadi kompromi yang masuk akal adalah untuk mengambil parameter nilai dan memiliki satu panggilan konstruktor gerakan ekstra untuk argumen nilai atau nilai sehubungan dengan implementasi yang optimal, yang juga merupakan saran yang diberikan dalam pembicaraan Herb.
Seperti @ JDługosz tunjukkan dalam komentar, melewati nilai hanya masuk akal untuk fungsi yang akan membangun beberapa objek dari argumen wastafel. Ketika Anda memiliki fungsi f
yang menyalin argumennya, pendekatan pass-by-value akan memiliki lebih banyak overhead daripada pendekatan umum pass-by-const-reference. Pendekatan nilai demi nilai untuk fungsi f
yang menyimpan salinan parameternya akan berbentuk:
void f(T x) {
T y{...};
...
y = std::move(x);
}
Dalam hal ini, ada konstruksi salinan dan tugas pemindahan untuk argumen nilai, dan konstruksi pemindahan dan pemindahan tugas untuk argumen nilai. Kasus paling optimal untuk argumen nilai adalah:
void f(const T& x) {
T y{...};
...
y = x;
}
Ini bermuara pada penugasan saja, yang berpotensi jauh lebih murah daripada copy constructor ditambah tugas penugasan yang diperlukan untuk pendekatan pass-by-value. Alasan untuk ini adalah bahwa penugasan mungkin menggunakan kembali memori yang dialokasikan yang ada di y
, dan karena itu mencegah alokasi (de), sedangkan copy constructor biasanya akan mengalokasikan memori.
Untuk argumen nilai, implementasi paling optimal untuk f
yang mempertahankan salinan memiliki bentuk:
void f(T&& x) {
T y{...};
...
y = std::move(x);
}
Jadi, hanya tugas pindah dalam hal ini. Melewati nilai ke versi f
yang mengambil referensi konst hanya biaya tugas alih-alih tugas pindah. Jadi relatif berbicara, versi f
mengambil referensi const dalam hal ini sebagai implementasi umum lebih disukai.
Jadi secara umum, untuk implementasi yang paling optimal, Anda perlu membebani atau melakukan semacam penerusan sempurna seperti yang ditunjukkan dalam pembicaraan. Kekurangannya adalah ledakan kombinatorial dalam jumlah kelebihan yang dibutuhkan, tergantung pada jumlah parameter f
jika Anda memilih untuk kelebihan pada kategori nilai argumen. Penerusan sempurna memiliki kelemahan yang f
menjadi fungsi templat, yang mencegah membuatnya virtual, dan menghasilkan kode yang jauh lebih kompleks jika Anda ingin mendapatkannya 100% benar (lihat pembicaraan untuk detail berdarah).