Понимание выражения lvalue / rvalue против типа объекта - PullRequest
0 голосов
/ 23 января 2019

Я читал некоторые из предыдущих топовых ответов, а также «Язык программирования C ++» и «Эффективный современный C ++» Страуструпа, но у меня возникают проблемы с пониманием различия между аспектом выражения lvalue / rvalue и его выражением. тип. Во введении к «Эффективному современному C ++» говорится:

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

Я чего-то не понимаю, потому что не понимаю, почему, если у вас есть параметр ссылочного типа rvalue, вам нужно фактически преобразовать его в rvalue через std::move(), чтобы сделать его пригодным для перемещения. Даже если параметр (все параметры) является lvalue, компилятор знает, что его тип является ссылкой на rvalue, так почему нужно указывать компилятору, что он может быть перемещен? Это кажется излишним, но я предполагаю, что не понимаю различия между типом выражения и его lvalue / rvalue природой (не уверен в правильной терминологии).

Edit:

Чтобы проконтролировать некоторые из ответов / комментариев ниже, все еще неясно, почему в doSomething() ниже мне нужно было бы обернуть параметр в std::move(), чтобы он связывался со ссылкой на rvalue и преобразовывался в 2-я версия doSomethingElse(). Я понимаю, что если бы это произошло неявно, это было бы плохо, потому что параметр был бы перемещен, и после этого его можно было бы непреднамеренно использовать. Похоже, что природа ссылочного типа rvalue параметра не имеет смысла в функции, поскольку ее единственной целью было связать ее, чтобы разрешить правильную версию функции, учитывая, что в качестве аргумента было передано rvalue.

Widget getWidget();
void doSomethingElse(Widget& rhs);  // #1
void doSomethingElse(Widget&& rhs); // #2

void doSomething(Widget&& rhs) {
  // will call #1
  doSomethingElse(rhs);
  // will call #2
  doSomethingElse(std::move(rhs));      
}

int main() {
  doSomething(getWidget());
}

Ответы [ 3 ]

0 голосов
/ 23 января 2019

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

Как сказано в кавычках, типы и значения категории - это разные вещи.Параметр всегда является lvalue, даже его тип является rvalue-ссылкой;мы должны использовать std::move, чтобы связать его с rvalue-ссылкой.Предположим, что мы позволяем компилятору делать это неявно, как в следующем фрагменте кода,

void foo(std::string&& s);
void bar(std::string&& s) {

    foo(s);  

    // continue to use s...
    // oops, s might have been moved

    foo(std::string{}); // this is fine;
                        // the temporary will be destroyed after the full expression and won't be used later

}

Так что мы должны явно использовать std::move, чтобы сообщить компилятору, что мы знаем, что мы пытаемся сделать.

void bar(std::string&& s) {

    foo(std::move(s));  

    // we know that s might have been moved
}
0 голосов
/ 23 января 2019

Я думаю, вы на самом деле поняли различие между типом и категорией значения , поэтому я сосредоточусь на двух конкретных утверждениях / запросах:

Вам нужно на самом деле привести его к r-значению через std :: move (), чтобы он мог быть перемещен

В некотором роде, но не на самом деле.Приведение выражения, которое присваивает имя вашему объекту или ссылается на него, в значение r позволяет нам при разрешении перегрузки вызвать перегрузку функции, которая принимает Type&&.Обычно мы делаем это, когда хотим передать право собственности, но это не совсем то же самое, что сделать его «приемлемым для переезда», потому что переезд может не соответствовать тому, что вы в итоге делаете.В некотором смысле это придирки, хотя я думаю, что это важно понять.Потому что:

Даже если параметр (все параметры) является lvalue, компилятор знает, что его тип является ссылкой на rvalue, так почему нужно сообщать компилятору о его перемещении?

Если вы не напишите std::move(theThing), или если вещь временная (уже значение r), тогда это не значение r, и поэтому она не может привязываться к ссылке на значение rvalue.Вот как все это разработано и определено.Он специально сделан таким образом, чтобы выражение lvalue, выражение, которое называет вещь, вещь, которую вы не написали std::move(), не будет привязываться к ссылке rvalue .И поэтому либо ваша программа не будет компилироваться, либо, если возможно, вместо разрешения перегрузки будет выбрана версия функции, которая может быть const Type& - и мы знаем, что с этим не может быть никакой передачи права собственности.

tl; dr: компилятор не не знает, что его тип является ссылкой на rvalue, потому что он не один.Так же, как вы не можете сделать int& ref = 42, вы не можете сделать int x = 42; int&& ref = x;.В противном случае он попытался бы переместить все !Весь смысл в том, чтобы заставить определенные виды ссылок работать только с определенными типами выражений, чтобы мы могли использовать это для запуска вызовов к функциям копирования / перемещения в зависимости от ситуации с минимальным количеством машин на площадке вызовов.

0 голосов
/ 23 января 2019

RValue ссылки существуют для решения проблемы пересылки.Существующие правила вывода типов в C ++ сделали невозможной последовательную и разумную семантику перемещения.Итак, система типов была расширена, и были введены новые правила, чтобы сделать ее более сложной, но согласованной.

Это имеет смысл, только если вы посмотрите на нее с точки зрения решаемой проблемы.Вот хорошая ссылка , предназначенная только для объяснения ссылок RValue.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...