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 fyang 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 fdisesuaikan dengan argumen lvalue dan rvalue masing-masing. Untuk melihat mengapa hal ini terjadi, anggaplah fmengambil parameter nilai, di mana Tada beberapa salin dan pindahkan tipe konstruktif:
void f(T x) {
T y{std::move(x)};
}
Memanggil fdengan argumen lvalue akan menghasilkan copy constructor yang dipanggil untuk membangun x, dan move constructor dipanggil untuk membangun y. Di sisi lain, memanggil fdengan argumen nilai akan menyebabkan pemindah konstruktor dipanggil untuk membangun x, dan pemindah pemindah lain dipanggil untuk membangun y.
Secara umum, implementasi optimal funtuk 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 funtuk 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 fyang menyalin argumennya, pendekatan pass-by-value akan memiliki lebih banyak overhead daripada pendekatan umum pass-by-const-reference. Pendekatan nilai demi nilai untuk fungsi fyang 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 fyang 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 fyang mengambil referensi konst hanya biaya tugas alih-alih tugas pindah. Jadi relatif berbicara, versi fmengambil 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 fjika Anda memilih untuk kelebihan pada kategori nilai argumen. Penerusan sempurna memiliki kelemahan yang fmenjadi fungsi templat, yang mencegah membuatnya virtual, dan menghasilkan kode yang jauh lebih kompleks jika Anda ingin mendapatkannya 100% benar (lihat pembicaraan untuk detail berdarah).