Я смотрел Скотта Мейерса говорить о универсальных ссылках от конференции C ++ и Beyond 2012, и все имеет смысл до сих пор. Тем не менее, один из слушателей задает вопрос примерно через 50 минут, в котором мне тоже интересно. Мейерс говорит, что он не заботится об ответе, потому что это не идиоматично и глупо для его ума, но я все еще заинтересован.
Код представлен следующим образом:
// Typical function bodies with overloading:
void doWork(const Widget& param) // copy
{
// ops and exprs using param
}
void doWork(Widget&& param) // move
{
// ops and exprs using std::move(param)
}
// Typical function implementations with universal reference:
template <typename T>
void doWork(T&& param) // forward => copy and move
{
// ops and exprs using std::forward<T>(param)
}
Дело в том, что когда мы берем ссылку на rvalue, мы знаем, что у нас есть значение rvalue, поэтому мы должны std::move
это сохранить тот факт, что это ценность. Когда мы берем универсальную ссылку (T&&
, где T
это выводимый тип), мы хотим std::forward
сохранить тот факт, что это могло быть lvalue или rvalue.
Итак, вопрос: так как std::forward
сохраняет ли значение, переданное в функцию, было или lvalue или rvalue, и std::move
просто приводит свой аргумент к rvalue, мы могли бы просто использовать std::forward
везде? Было бы std::forward
вести себя как std::move
во всех случаях, когда мы будем использовать std::move
или есть некоторые важные различия в поведении, которые пропускаются обобщением Мейерса?
Я не предлагаю, чтобы кто-то делал это, потому что, как правильно говорит Мейерс, это совершенно не идиоматично, но это также правильное использование std::move
:
void doWork(Widget&& param) // move
{
// ops and exprs using std::forward<Widget>(param)
}
Два очень разные и дополнительный инструменты.
std::move
выводящей аргумент и безоговорочно создает выражение Rvalue. Это имеет смысл применять к реальному объекту или переменной.
std::forward
принимает обязательный аргумент шаблона (вы должны указать это!) и волшебным образом создает выражение lvalue или rvalue в зависимости от типа (в силу добавления &&
и рушатся правила). Это имеет смысл только для применения к выведенному аргументу шаблонной функции.
Возможно, следующие примеры иллюстрируют это немного лучше:
#include <utility>
#include <memory>
#include <vector>
#include "foo.hpp"
std::vector<std::unique_ptr<Foo>> v;
template <typename T, typename ...Args>
std::unique_ptr<T> make_unique(Args &&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); // #1
}
int main()
{
{
std::unique_ptr<Foo> p(new Foo('a', true, Bar(1,2,3)));
v.push_back(std::move(p)); // #2
}
{
v.push_back(make_unique<Foo>('b', false, Bar(5,6,7))); // #3
}
{
Bar b(4,5,6);
char c = 'x';
v.push_back(make_unique<Foo>(c, b.ready(), b)); // #4
}
}
В ситуации № 2 у нас есть конкретный объект p
и мы хотим от него уйти, безусловно. Только std::move
имеет смысл. Здесь нечего «пересылать». У нас есть именованная переменная, и мы хотим от нее отказаться.
С другой стороны, ситуация # 1 принимает список аргументов любого рода, и каждый аргумент должен быть перенаправлен в ту же категорию значений, что и в исходном вызове. Например, в # 3 аргументы являются временными выражениями, и, таким образом, они будут переданы как значения r. Но мы могли бы также смешать именованные объекты в вызове конструктора, как в ситуации # 4, и тогда нам нужно было бы пересылать как lvalues.
Да, если param
это Widget&&
, тогда следующие три выражения эквивалентны (при условии, что Widget
не является ссылочным типом):
std::move(param)
std::forward<Widget>(param)
static_cast<Widget&&>(param)
В общем (когда Widget
может быть ссылка), std::move(param)
эквивалентно обоим следующим выражениям:
std::forward<std::remove_reference<Widget>::type>(param)
static_cast<std::remove_reference<Widget>::type&&>(param)
Обратите внимание, насколько лучше std::move
для движущихся вещей. Точка std::forward
в том, что он хорошо сочетается с правилами вывода типа шаблона:
template<typename T>
void foo(T&& t) {
std::forward<T>(t);
std::move(t);
}
int main() {
int a{};
int const b{};
//Deduced T Signature Result of `forward<T>` Result of `move`
foo(a); //int& foo(int&) lvalue int xvalue int
foo(b); //int const& foo(int const&) lvalue int const xvalue int const
foo(int{});//int foo(int&&) xvalue int xvalue int
}