Оптимизация переезда
Рассмотрим две реализации класса:
struct S1
{
std::vector< T > v;
void push(T && x) { v.push_back(std::move(x)); }
void push(T const & x) { push(T(x)); }
void pop() { v.pop_back(); }
void replace(T && x) { pop(); push(std::move(x)); }
void replace(T const & x) { replace(T(x)); }
};
struct S2
{
std::vector< T > v;
void push(T x) { v.push_back(std::move(x)); }
void pop() { v.pop_back(); }
void replace(T x) { pop(); push(std::move(x)); }
};
S1
"s push
перегрузки выражают именно то, что я хочу. S2
"s push
это способ выразить это в менее многословной форме.
Но я волнуюсь, что есть недостаток, связанный с чрезмерным движением-строительством объектов.
Могут ли современные компиляторы уменьшить выражение std::move(T(std::move(t)))
в std::move(t)
для некоторых t
где decltype(t)
является T&
? Могут ли современные компиляторы оптимизировать ненужные шаги? Или это запрещено стандартом?
1 ответ
Нет, это решение не является законным, кроме как при оптимизации "как будто".
Сейчас если foo()
это выражение, которое возвращает T
, затем S{}.push(foo())
может исключить движение из возвращаемого значения foo()
в аргумент push
: только один ход сделан.
Но если мы S{}.push(std::move(foo())
явное std::move
блокирует возможность исключения.
Подход, который часто лучше, - это использование операций на основе операций вместо операций на основе push.
template<class...Args>
void emplace(Args&&...args) {
v.emplace_back( std::forward<Args>(args)... );
}
это позволяет передавать параметры для построения T
к объекту, и заставить его быть непосредственно построен в приемнике (вектор), а не перемещен или скопирован в него.
По выбору:
template<class...Args,
decltype(T(std::declval<Args&&>()...))* =0
>
void emplace(Args&&...args) {
v.emplace_back( std::forward<Args>(args)... );
}
если вы хотите поддержку SFINAE. Комментарий, говорящий "мы ожидаем T
быть построенным здесь ", если не очевидно, также вежливо.