Оптимизация переезда

Рассмотрим две реализации класса:

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 быть построенным здесь ", если не очевидно, также вежливо.

Другие вопросы по тегам