Есть много распространенных заблуждений в вопросе и других ответах;мой ответ надеется решить эту проблему.
Термины lvalue и rvalue являются категориями выражений .Это термины, которые применяются к выражениям.Не к объектам.(Немного смущает, официальный термин для категорий выражений - «категории значений»!)
Термин временный объект относится к объектам.Это включает в себя объекты типа класса, а также объекты встроенного типа.Термин временный (используется как существительное) является сокращением от временный объект .Иногда автономный термин значение используется для обозначения временного объекта встроенного типа.Эти термины применяются к объектам, а не к выражениям.
Стандарт C ++ 17 более согласован в терминологии объектов, чем предыдущие стандарты, например, см. [Conv.rval] / 1.Теперь он пытается не произносить значение , кроме как в контексте значение выражения .
Теперь, почему существуют разные категории выражений?Программа на C ++ состоит из набора выражений, соединенных друг с другом операторами для создания больших выражений;и вписывание в рамки декларативных конструкций.Эти выражения создают, уничтожают и делают другие манипуляции с объектами.Программирование на C ++ можно описать как использование выражений для выполнения операций с объектами.
Причина, по которой существуют категории выражений, заключается в предоставлении основы для использования выражений для выражения операций, которые программист намеревается.Например, еще в дни Си (и, возможно, раньше) разработчики языка полагали, что 3 = 5;
не имеет никакого смысла как часть программы, поэтому было решено ограничить то, какое выражение может появляться в левой части.=
, и пусть компилятор сообщит об ошибке, если это ограничение не соблюдалось.
Термин lvalue возник в те дни, хотя сейчас с развитием C ++ существуетширокий спектр выражений и контекстов, в которых полезны категории выражений, а не только левая часть оператора присваивания.
Вот правильный код C ++: std::string("3") = std::string("5");
.Концептуально это ничем не отличается от 3 = 5;
, однако допускается.В результате создается временный объект типа std::string
и содержимое "3"
, затем этот временный объект изменяется на содержимое "5"
, а затем временный объект уничтожается.Язык мог бы быть спроектирован так, чтобы код 3 = 5;
определял подобную серию событий (но это не так).
Почему пример string
допустим, а пример int
не?
Каждое выражение должно иметь категорию.На первый взгляд категория выражения может не иметь очевидной причины, но разработчики языка дали каждому выражению категорию в соответствии с тем, что, по их мнению, является полезным понятием для выражения, а что - нет.
Было решено, что последовательность событий в 3 = 5;
, как описано выше, не является чем-то, что кто-то хотел бы сделать, и если кто-то написал такую вещь, он, вероятно, допустил ошибку и имел в виду что-то другое, поэтому компилятор должен помочьсообщая об ошибке.
Теперь та же логика может заключить, что std::string("3") = std::string("5")
- это не то, что кто-либо когда-либо захочет сделать.Однако другой аргумент заключается в том, что для некоторого другого типа класса T(foo) = x;
может быть полезной операцией, например, потому что T
может иметь деструктор, который что-то делает.Было решено, что запрет такого использования может быть более вредным для намерений программиста, чем пользой.(Независимо от того, что это было хорошее решение или не является спорным; см этот вопрос для обсуждения)
Теперь мы приближаемся, наконец, ответ на свой вопрос:.)
Наличие или отсутствие памяти или места хранения больше не является обоснованием для категорий выражений.В абстрактной машине (более подробное объяснение этого ниже) каждый временный объект (включая объект, созданный 3
в x = 3;
) существует в памяти.
Как описано ранее вмой ответ, программа состоит из выражений, которые манипулируют объектами.Каждое выражение называется обозначает или относится к объекту.
В других ответах или статьях по этой теме очень часто делается неверное утверждение, что rvalue может обозначать только временный объект или, что еще хуже, rvalue является временным объектом,или что временный объект является значением.Выражение не является объектом, это то, что происходит в исходном коде для манипулирования объектами!
Фактически временный объект может быть обозначен выражением lvalue или rvalue;и невременный объект может быть обозначен lvalue или выражением rvalue.Это отдельные понятия.
Теперь есть правило категории выражений, которое нельзя применять &
к выражению категории rvalue.Цель этого правила и этих категорий состоит в том, чтобы избежать ошибок, когда временный объект используется после его уничтожения.Например:
int *p = &5; // not allowed due to category rules
*p = 6; // oops, dangling pointer
Но вы можете обойти это:
template<typename T> auto f(T&&t) -> T& { return t; }
// ...
int *p = f(5); // Allowed
*p = 6; // Oops, dangling pointer, no compiler error message.
В этом последнем коде f(5)
и *p
оба являются lvalue, которые обозначают временный объект.Это хороший пример того, почему существуют правила категории выражений;следуя правилам без хитрого обходного пути, мы получим ошибку для кода, который пытается написать через висячий указатель.
Обратите внимание, что вы также можете использовать этот f
, чтобы найти адрес памятивременный объект, например, std::cout << &f(5);
Таким образом, все вопросы, которые вы фактически задаете, ошибочно связывают выражения с объектами.Таким образом, они не являются вопросами в этом смысле.Временные значения не являются lvalue, потому что объекты не являются выражениями.
Допустимый, но связанный с этим вопрос: «Почему выражение, создающее временный объект, является rvalue (а не lvalue?)»
Ответ на который был, как обсуждалось выше: наличие lvalue увеличило бы риск создания висячих указателей или висячих ссылок;и, как в 3 = 5;
, увеличился бы риск указания избыточных операций, которые программист, вероятно, не намеревался.
Еще раз повторяю, что категории выражений - это дизайнерское решение, помогающее программистам выразиться;не имеет ничего общего с памятью или местами хранения.
Наконец, к абстрактной машине и как-будто правилу .C ++ определяется в терминах абстрактной машины, в которой временные объекты также имеют память и адреса.Ранее я привел пример того, как напечатать адрес временного объекта.
Правило as-if гласит, что выходные данные фактического исполняемого файла, создаваемого компилятором, должны совпадать только с выходными данными абстрактной машины.На самом деле исполняемый файл не должен работать так же, как абстрактная машина, он просто должен давать тот же результат.
То есть для кода, подобного x = 5;
, даже если временный объект со значением 5
имеет место в памяти в абстрактной машине;компилятору не нужно выделять физическую память на реальной машине.Нужно только убедиться, что x
в конечном итоге хранит 5
в нем, и есть гораздо более простые способы сделать это, не включая создание дополнительного хранилища.
как-будтоПравило применяется ко всему в программе, хотя мой пример здесь относится только к временным объектам.Невременный объект также можно оптимизировать, например, int x; int y = 5; x = y; // other code that doesn't use y
можно изменить на int x = 5;
.
То же самое относится к типам классов без побочных эффектов, которые могли бы изменить вывод программы.Например, std::string x = "foo"; std::cout << x;
можно оптимизировать до std::cout << "foo";
, хотя lvalue x
обозначает объект с хранилищем в абстрактной машине.