Почему параметры универсального эталона должны быть отлиты перед использованием?

в лекция об универсальных ссылках, Скотт Мейерс (примерно на 40-й минуте) сказал, что объекты, которые являются универсальными ссылками, должны быть преобразованы в реальный тип перед использованием. Другими словами, всякий раз, когда есть шаблонная функция с универсальным ссылочным типом, std::forward следует использовать перед использованием операторов и выражений, в противном случае можно сделать копию объекта.

Мое понимание этого в следующем примере:

#include <iostream>

struct A
{
A() { std::cout<<"constr"<<std::endl; }
A(const A&) { std::cout<<"copy constr"<<std::endl; }
A(A&&) { std::cout<<"move constr"<<std::endl; }
A& operator=(const A&) { std::cout<<"copy assign"<<std::endl; return *this; }
A& operator=(A&&) { std::cout<<"move assign"<<std::endl; return *this; }

~A() { std::cout<<"destr"<<std::endl; }

void bar()
{
std::cout<<"bar"<<std::endl;
}
};

A getA()
{
A a;
return a;
}

template< typename T >
void callBar( T && a )
{
std::forward< T >( a ).bar();
}

int main()
{
{
std::cout<<"\n1"<<std::endl;
A a;
callBar( a );
}

{
std::cout<<"\n2"<<std::endl;
callBar( getA() );
}
}

Как и ожидалось, на выходе получается:

1
constr
bar
destr

2
constr
move constr
destr
bar
destr

Вопрос действительно в том, зачем это нужно?

std::forward< T >( a ).bar();

Я попытался без std :: forward, и, кажется, работает нормально (вывод тот же).

Точно так же, почему он рекомендует использовать move внутри функции с rvalue? (ответ такой же, как и для std :: forward)

void callBar( A && a )
{
std::move(a).bar();
}

Я понимаю что оба std::move а также std::forward Это просто приведение к соответствующим типам, но действительно ли это необходимо в приведенном выше примере?

Бонус: как можно изменить пример, чтобы получить копию объекта, который передается этой функции?

6

Решение

Это нужно, потому что bar() может быть перегружен отдельно для значений и значений. Это означает, что он может сделать что-то по-другому, или может быть не разрешено, в зависимости от того, правильно ли вы описали a как lvalue или rvalue, или просто слепо относился к нему как lvalue. В настоящее время большинство пользователей не используют эту функцию и не имеют к ней доступа, потому что большинство популярных компиляторов не поддерживают ее — даже GCC 4.8 не поддерживает rvalue *this, Но это стандарт.

2

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

Есть два разных использования && по параметру функции. Для обычной функции это означает, что аргумент является ссылкой на значение; для функции шаблона это означает, что она может быть или ссылка rvalue или ссылка lvalue:

template <class T> void f(T&&); // rvalue or lvalue
void g(T&&);                    // rvalue only
void g(T&)                      // lvalue only

void h() {
C c;
f(c);            // okay: calls f(T&)
f(std::move(c)); // okay: calls f(T&&)
g(c);            // error: c is not an rvalue
g(std::move(c)); // okay: move turns c into an rvalue
}

внутри f а также g, применяя std::forward для такого аргумента сохраняется lvalue- или rvalue-ness-аргумента, поэтому в общем случае это самый безопасный способ пересылки аргумента в другую функцию.

2

void callBar( A && a )
{
std::move(a).bar();
}

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

Сам параметр является именующий, потому что это именованная вещь. Вы можете взять его адрес.

Таким образом, чтобы сделать его снова значимым и уйти от него, необходимо применить std::move к этому. Если бы вы буквально просто вызывали функцию с переданным параметром, я не понимаю, почему у вас есть параметр, являющийся ссылкой на rvalue.

Вы хотите передать ссылку на rvalue только в том случае, если вы собираетесь отойти от этого внутри своей функции, поэтому вам придется использовать std::move,

Ваш пример здесь на самом деле не имеет большого смысла в этом отношении.

1

В лекции сказано следующее:

void doWork( Widget&& param )
{
ops and exprs using std::move(param)
}

SM: Что это означает: если вы видите код, который принимает ссылку на rvalue, и вы видите использование этого параметра без переноса, это весьма подозрительно.

Подумав немного, я понял, что это правильно (как и ожидалось). Изменение callBar Функция в исходном примере для демонстрации этого:

void reallyCallBar( A& la )
{
std::cout<<"lvalue"<<std::endl;
la.bar();
}

void reallyCallBar( A&& ra )
{
std::cout<<"rvalue"<<std::endl;
ra.bar();
}

template< typename T >
void callBar( T && a )
{
reallyCallBar( std::forward< T >( a ) );
}

Если std::forward не использовался в callBarтогда reallyCallBar( A& ) будет использоваться. Так как a в callBar является ссылкой на lvalue. std::forward делает его r-значением, когда универсальная ссылка является ссылкой-значением.

Следующая модификация еще раз подтверждает это:

void reallyCallBar( A& la )
{
std::cout<<"lvalue"<<std::endl;
la.bar();
}

void reallyCallBar( A&& ra )
{
std::cout<<"rvalue"<<std::endl;
reallyCallBar( ra );
}

template< typename T >
void callBar( T && a )
{
reallyCallBar( std::forward< T >( a ) );
}

поскольку std::move не используется в reallyCallBar( A&& ra ) функция, она не входит в бесконечный цикл. Вместо этого он вызывает версию со ссылкой на lvalue.

Поэтому (как объяснено в лекции):

  • std::forward должен использоваться на универсальных ссылках
  • std::move должен использоваться в ссылках rvalue
0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector