Як @ JDługosz в коментарях зазначає, Герб дає інші поради в іншій (пізніше?) Розмові, дивіться приблизно звідси: https://youtu.be/xnqTKD8uD64?t=54m50s .
Його порада зводиться лише до використання параметрів значення для функції, f
яка приймає так звані аргументи занурення, припускаючи, що ви перемістите конструкцію з цих аргументів раковини.
Цей загальний підхід лише додає накладні витрати конструктора руху для аргументів lvalue та rvalue порівняно з оптимальною реалізацією f
відповідно до аргументів lvalue та rvalue. Щоб зрозуміти, чому це так, припустимо, f
приймає параметр значення, де T
знаходиться деяка копія та переміщення сконструйованого типу:
void f(T x) {
T y{std::move(x)};
}
Виклик f
аргументом lvalue призведе до того, що конструктор копії буде викликаний для побудови x
, а конструктор переміщення буде викликаний для побудови y
. З іншого боку, виклик f
з аргументом rvalue викликає конструктор переміщення, який буде викликаний для побудови x
, а інший конструктор переміщення буде викликаний для побудови y
.
Загалом, оптимальна реалізація f
аргументів lvalue полягає в наступному:
void f(const T& x) {
T y{x};
}
У цьому випадку для конструювання викликається лише один конструктор копій y
. Оптимальною реалізацією f
аргументів для rvalue є, знову ж таки, загалом таке:
void f(T&& x) {
T y{std::move(x)};
}
У цьому випадку для побудови викликається лише один конструктор переміщення y
.
Отже, розумний компроміс - це прийняти параметр значення та мати додатковий виклик конструктора ходу для аргументів lvalue або rvalue щодо оптимальної реалізації, що також є порадою, наданою в розмові Herb.
Як в коментарях зазначав @ JDługosz, передача значення має сенс лише для функцій, які будуватимуть якийсь об'єкт з аргументу потоку. Якщо у вас є функція, f
яка копіює її аргумент, підхід "передача за значенням" матиме більше накладних витрат, ніж загальний підхід "по базі". Підхід передачі значення для функції, f
що зберігає копію свого параметра, матиме вигляд:
void f(T x) {
T y{...};
...
y = std::move(x);
}
У цьому випадку є аргумент копії та призначення переміщення для аргументу lvalue, а також переміщення конструкції та призначення переміщення для аргументу rvalue. Найбільш оптимальним випадком аргументу lvalue є:
void f(const T& x) {
T y{...};
...
y = x;
}
Це зводиться лише до призначення, що потенційно набагато дешевше, ніж конструктор копій плюс присвоєння переміщення, необхідний для підходу прохідної вартості. Причиною цього є те, що присвоєння може повторно використовувати наявну виділену пам'ять у y
, а отже, запобігати (де) виділенням, тоді як конструктор копій зазвичай виділяє пам'ять.
Для аргументу rvalue найоптимальнішою реалізацією для f
збереження копії є така форма:
void f(T&& x) {
T y{...};
...
y = std::move(x);
}
Отже, лише призначення руху в цьому випадку. Передача rvalue до версії, f
яка має посилання const, коштує лише призначення замість призначення переміщення. Таким чином, відносно кажучи, f
переважнішим є варіант прийняття посилання в цьому випадку як загальної реалізації.
Тож загалом для найбільш оптимальної реалізації вам потрібно буде перевантажувати або робити якесь ідеальне переадресація, як показано в бесіді. Недолік - це комбінаторний вибух у кількості необхідних перевантажень, залежно від кількості параметрів, f
якщо ви вирішите перевантажувати категорію значень аргументу. Ідеальне переадресація має недолік, який f
стає функцією шаблону, що перешкоджає зробити його віртуальним, і приводить до значно складнішого коду, якщо ви хочете отримати його на 100% правильно (див. Розмову про деталі горі).