Std:: перемещение в списке инициализаторов конструктора в шаблоне класса

У меня есть такой шаблон:

template<typename T>
struct foo {
  T m_t;
  foo(T t) : m_t(t) {}
};

Проблема в том, что я хочу поддерживать как мелкие/обычные типы, так и огромные типы (например, матрицы) для T. Рекомендуете ли вы написать список инициализаторов конструктора, как этот

foo (T t) : m_t(std::move(t)) {}

и требуют, чтобы тип T всегда поддерживал конструкцию перемещения даже для меньших типов? Есть ли лучшие способы?

Ответ 1

и требуют, чтобы тип T всегда поддерживал конструкцию перемещения даже для меньших типов?

Любой тип, который является конструируемым, также перемещается конструктивно. Перемещение в этих случаях просто вызывает конструктор копирования. Таким образом, нет оснований не использовать m_t(std::move(t)).

Альтернативой является использование ссылок вместо:

foo (T const& t) : m_t(t) {}
foo (T&& t) : m_t(std::move(t)) {}

Это может быть связано только с одной конструкцией, а не с двумя.

Ответ 2

Да, использование этого хода не имеет недостатка в этой ситуации. Все объекты, подлежащие копированию, автоматически перемещаются, поэтому это не имеет значения. На самом деле, некоторые рекомендуют всегда перемещать переменные, когда это возможно, даже целые числа.

В качестве альтернативы вы можете использовать совершенную переадресацию, как описано в этом ответе:

template <typename T2>
foo(T2&& t) : m_t(std::forward<T2>(t)) {}

Если вы знаете, что T определяет конструктор быстрого перемещения, это не имеет значения. В противном случае рекомендуется предоставить конструктор foo(const T&), чтобы избежать ненужных копий.

Идеальная пересылка - это только один способ достижения этого. Разумеется, решение Pubby для выписывания конструктора foo(const T&) и foo(T&&) также отлично. Результаты те же, это в основном вопрос стиля.

Вы также задали вопрос о небольших целых типах. Теоретически передача их по ссылке медленнее, чем копирование их, но компилятор должен иметь возможность оптимизировать его для копии. Я не думаю, что это будет иметь значение.

Итак, лучше оптимизируйте для худшего случая, когда T может быть огромным и не обеспечивает быстрый конструктор перемещения. Передача по ссылке лучше для этой ситуации и не должна быть плохим выбором в целом.

Ответ 3

Передача по значению имеет то преимущество, что имеет только один конструктор (без шаблона), но стоит цена одной дополнительной конструкции перемещения по сравнению с вышеупомянутыми альтернативами.

Тем не менее, существует еще один, но не ограниченный недостаток как пропущенного значения, так и решения без шаблонов, заданного Pubby: Moving не будет работать, если конструктор копирования определяется как T(T&); (обратите внимание на ссылку на не- Const). Rvalue-ссылки могут связываться с lvalue-reference-to-const, но не с lvalue-reference-to-non-const, поэтому компилятор не может вызвать конструктор копирования.

Чтобы решить эту проблему, вы можете просто добавить третью перегрузку foo (T & t) : m_t(t) {} в решение Pubby.