Ответ 1
Решение, которое вы предлагаете:
void add(T value) {
_impl.push_back(value);
}
Требуется некоторая корректировка, так как вы всегда будете выполнять одну копию value
, даже если вы передадите rvalue на add()
(две копии, если вы передадите lvalue): поскольку value
является lvalue, компилятор автоматически не переместится от него, когда вы передадите его как аргумент push_back
.
Вместо этого вы должны сделать следующее:
void add(T value) {
_impl.push_back(std::move(value));
// ^^^^^^^^^
}
Это лучше, но все еще недостаточно хорошо для кода шаблона, потому что вы не знаете, стоит ли T
дешево или дорого двигаться. Если T
является POD следующим образом:
struct X
{
double x;
int i;
char arr[255];
};
Тогда перемещение будет не быстрее, чем копировать его (на самом деле, перемещение было бы таким же, как копирование). Поскольку ваш общий код должен избегать ненужных операций (и что, поскольку эти операции могут быть дорогими для некоторых типов), вы не можете позволить себе принимать параметр по значению.
Одним из возможных решений (принятым стандартной библиотекой С++) является предоставление двух перегрузок add()
, один из которых принимает опорную ссылку lvalue, а другой - для ссылки rvalue:
void add(T const& val) { _impl.push_back(val); }
void add(T&& val) { _impl.push_back(std::move(val)); }
Еще одна возможность - предоставить версию шаблона версии add()
(возможно, с ограничением SFINAE), которая будет принимать так называемую универсальную ссылку (нестандартный термин, придуманный Скоттом Мейерсом):
template<typename U>
void add(U&& val) { _impl.push_back(std::forward<U>(val)); }
Оба этих решения оптимальны в том смысле, что только одна копия выполняется, когда lvalues предоставляются, и только один шаг выполняется, когда rvalues предоставляются.