Могу ли я / всегда использовать std :: forward вместо std :: move?

Я смотрел Скотта Мейерса говорить о универсальных ссылках от конференции 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)
}

62

Решение

Два очень разные и дополнительный инструменты.

  • 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.

69

Другие решения

Да, если 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
}
14

По вопросам рекламы [email protected]