Введение в Rvalue и Lvalue

С появлением C ++ 11 появилась возможность еще больше повысить производительность и гибкость приложений C ++ благодаря введению семантики перемещения и идеальной пересылки. Две концепции, основанные на ссылках на rvalue.

Прежде чем обсуждать эти новые концепции, необходимо понять rvalues ​​и lvalues. Эти концепции сами по себе не слишком сложны, но развивающаяся природа C ++ и изменения в определении этих значений по мере развития языка могут сделать их довольно запутанными для новых разработчиков.

Вначале rvalue и lvalue были определены таким образом, что rvalue очень просто означало что-то, что появляется в правой части присваивания, а lvalue - слева. Например:

int num = 3;
4 = num; // 4 is a literal which is an rvalue and num is an lvalue thus the following code isn’t valid

Но допустимо что-то вроде следующего:

int num = 4; // num is an lvalue and 4 is an rvalue

Время шло, и язык C ++ развивался, вместе с определениями rvalues ​​и lvalues. К сожалению, их имена не эволюционировали вместе с этим. В более современных версиях C ++ rvalues ​​и lvalues ​​приобрели новое значение.

Lvalues ​​и rvalues ​​стали чем-то, что использовалось для описания результатов выражения (выражение - это любой код, возвращающий значение), и как такое выражение можно было бы описать либо как выражение lvalue, либо как выражение rvalue.

Для компилятора это, помимо прочего, определяет, как сохраняется результат выражения. Для разработчика, как я предполагаю, это в основном означает, что lvalue имеет имя и идентифицируемое место в памяти, к которому можно получить доступ с помощью ‘&’. Rvalue - это то, что не имеет идентифицируемого местоположения в памяти, и поэтому к нему нельзя получить доступ с помощью «&».

Простой способ определить, возвращает ли выражение rvalue или lvalue, - это определить, можете ли вы взять адрес значения. Если вы можете это lvalue, иначе это rvalue.

Значения R являются временными и имеют короткое время жизни, если они существуют в выражении, которое они были созданы. Из-за этого C ++ не разрешает доступ к ним через адрес оператора из-за большого количества проблем, которые могут возникнуть при попытке доступа к адресу короткоживущих данных.

На этом все заканчивается тем, что такое rvalues ​​и lvalues, но я могу предположить, что приведенное выше определение мало что дает для объяснения их фактического использования при написании приложений на C ++. Я попытаюсь исправить это в следующем разделе, объяснив, как они могут вызывать проблемы при написании кода.

Как упоминалось ранее, вы не можете получить адрес rvalue, потому что C ++ не позволяет этого. Это означает, что при написании кода существует множество значений, на которые мы не можем ссылаться. Литералы, функции, возвращающие по значению, и временные объекты - лишь несколько примеров. Это означает, что ссылка на эти значения не может быть сохранена, но, что более важно, эти значения не могут быть переданы функциям, которые принимают ссылочные типы.

К сожалению, передача по ссылке - это распространенный метод повышения производительности приложения, поскольку он позволяет избежать необходимости копировать данные между ячейками памяти, что при большом количестве данных может занять слишком много времени в наших критичных по времени приложениях.

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

int sum(int& a, int& b) { return a + b; 
Sum(3, 5);

Надо было бы написать так:

int sum(int& a, int& b) { return a + b; 
int a = 5; 
int b = 3;
int c = sum(a, b);

Удобство использования rvalue теряется для поддержания производительности, но за счет удобочитаемости и времени. Второй вариант - передать по значению, но это потенциально может снизить производительность. Существует третий вариант - передача по ссылке, но с сохранением параметра функции константой.

int sum(const int& a, const int& b) { return a + b; 
Sum(3, 5);

C ++ позволяет присвоить rvalue ссылке, которая является lvalue, тогда и только тогда, когда это lvalue определено как const, что предотвращает манипулирование им. Это означает, что rvalue технически является lvalue в рамках функции, и теперь функция может принимать rvalue, сохраняя при этом передачу по ссылке.

Возникает очевидная проблема: если функции необходимо манипулировать ссылкой, она не может без перегрузки этой функции неконстантным вариантом, когда требуется передать lvalue. Rvalues ​​вызывает дополнительные проблемы в C ++, которые не решались до C ++ 11.

Например, C ++ имеет тенденцию чрезмерно баловаться, когда дело доходит до копирования значений, и есть проблемы с пересылкой значений функциям, вызываемым в шаблонных функциях. С этими проблемами борются с помощью семантики перемещения и идеальной пересылки, которую будет намного легче понять теперь, когда у вас есть понимание rvalue и lvalues, и она будет в центре внимания следующих нескольких статей.

Как всегда, если у вас есть вопросы, напишите мне @gamedevunboxed :)