В чем разница между std:: move и std:: forward

Я видел это здесь: Move Constructor вызов базового класса Move Constructor

может кто-нибудь объяснить:

  1. разницу между std::move и std::forward, желательно с примерами кода?
  2. как думать об этом легко, а когда следует использовать

3 ответов


std::move принимает объект и позволяет рассматривать его как временный (rvalue). Хотя это не является семантическим требованием, обычно функция, принимающая ссылку на rvalue, аннулирует ее. Когда вы видите std::move, это означает, что значение объекта не должно использоваться впоследствии, но вы все равно можете назначить новое значение и продолжить его использование.

std::forward имеет один вариант использования: привести параметр шаблонной функции (внутри функции) к категории значений (lvalue или rvalue) вызывающий абонент использовал, чтобы передать его. Это позволяет аргументам rvalue передаваться как rvalues, а lvalues-как lvalues, схема под названием "Идеальная пересылка."

до иллюстрации:

void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }

template< typename t >
/* "t &&" with "t" being template param is special, and  adjusts "t" to be
   (for example) "int &" or non-ref "int" so std::forward knows what to do. */
void forwarding( t && arg ) {
    std::cout << "via std::forward: ";
    overloaded( std::forward< t >( arg ) );
    std::cout << "via std::move: ";
    overloaded( std::move( arg ) ); // conceptually this would invalidate arg
    std::cout << "by simple passing: ";
    overloaded( arg );
}

int main() {
    std::cout << "initial caller passes rvalue:\n";
    forwarding( 5 );
    std::cout << "initial caller passes lvalue:\n";
    int x = 5;
    forwarding( x );
}

как упоминает Говард, есть также сходства, поскольку обе эти функции просто приведены к ссылочному типу. Но вне этих конкретных случаев использования (которые охватывают 99,9% полезности ссылок rvalue), вы должны использовать static_cast напрямую и написать хорошее объяснение того, что ты делаешь.


и std::forward и std::move не бросает.

X x;
std::move(x);

вышеуказанное бросает выражение lvalue x типа X для выражения rvalue типа X (xvalue, чтобы быть точным). move может также принять rvalue:

std::move(make_X());

и в этом случае это функция идентификации: принимает rvalue типа X и возвращает rvalue типа X

С std::forward вы можете выбрать пункт назначения в какой-то степени:

X x;
std::forward<Y>(x);

бросает именующее выражение x типа X для выражения типа Y. существуют ограничения на то, что Y может быть.

Y может быть доступной базой X, или ссылка на базу X. Y может быть X, или ссылка на X. нельзя отбрасывать CV-квалификаторы с forward, но можно добавить cv-квалификаторы. Y не может быть типом, который просто конвертируется из X, за исключением доступного базового преобразования.

если y является ссылкой lvalue, результатом будет выражение lvalue. Если Y не является ссылкой lvalue, результатом будет выражение rvalue (xvalue, чтобы быть точным).

forward может принимать аргумент rvalue, только если Y не является ссылкой lvalue. То есть вы не можете бросить rvalue в lvalue. Это по соображениям безопасности, поскольку это обычно приводит к болтающимся ссылкам. Но приведение rvalue к rvalue в порядке и разрешено.

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


std::forward используется вперед параметр точно так же, как он был передан функции. Как показано здесь:

когда использовать std:: forward для пересылки аргументов?

используя std::move предлагает объект как rvalue, возможно, соответствующий конструктору перемещения или функции, принимающей rvalues. Он делает это для std::move(x) даже если x само по себе не является rvalue.