Значение против объекта
[С++, 17]
Когда выражение prvalue оценивается, стандарт говорит, что оно приводит к значению. В случае 5
выражение является предварительным значением и оценивается как значение 5
.
Однако, когда у вас есть значение, в основном инициализатор для объекта, такой как Foo{}
. Какова будет ценность этого выражения? Будет ли результат временным объектом, созданным преобразованием prvalue-xvalue? Это поднимает мой более широкий вопрос о разнице между ценностью и объектом.
Ответы
Ответ 1
[intro.object]/1 :
Объект создается по определению, выражению new, при неявном изменении активного члена объединения или при создании временного объекта. Объект занимает область хранения в период его строительства, на протяжении всей его жизни и в период его разрушения.
Независимо от того, имеет ли prvalue тип класса, такой как Foo{}
или нет, так как литерал 5
считается значением, и это значение затем используется для инициализации объекта, если это действительно необходимо, это когда значение материализуется в объект.
[class.temporary]/2:
Материализация временного объекта обычно задерживается как можно дольше, чтобы избежать создания ненужных временных объектов.
В том же разделе вы найдете список, описывающий, когда временные материализовались.
Ответ 2
Значение является абстрактным понятием. Значение связано с набором реализаций, которые характеризуют или идентифицируют значение. Например, со значением 10 $ можно было купить книгу или еду.
Значение может иметь несколько представлений. Например, 10 $ могут быть представлены монетами или сохранены в виде битов на банковском счете.
Объектом является значение, которое является банковским счетом для некоторого количества денег: объект (/банковский счет) содержит представление значения (/10 $). Это описано в [basic.types]:
Представление значения объекта типа T - это набор битов, которые участвуют в представлении значения типа T. Биты в представлении объекта, которые не являются частью представления значения, являются битами заполнения.
Затем в [intro.object] указывается, что объект связан с областью хранения:
Объект занимает область хранения в период его строительства ([class.cdtor]), в течение всего срока его службы и в период разрушения ([class.cdtor]).
Это различие между объектом и его значением является более разумным, если мы рассмотрим абстрактную машину с центральным процессором, который выполняет операции, и отдельной памятью, где могут храниться объекты (которые содержат представление значения). Когда операция выполняется над значением, оно загружается в разные регистры процессора. Таким образом, значение в процессоре не имеет того же представления: непрерывная последовательность битов, как это было внутри объекта. Более того, любой процессор может свободно представлять значение в регистре как наиболее подходящий для его нужд.
Когда процессор выполняет операцию, он выполняет ее на части значения, хранящейся в регистре. После выполнения этой операции процессор может сохранить результат в памяти, внутри объекта или продолжить работу со значением.
Декомпозиция операции на операции со значениями и в хранилищах или нагрузках от или к объектам представлена в стандарте:
-
загрузка - это преобразование из lvalue-в-значение [conv.lvalue] glvalue нефункционального типа, не являющегося массивом T, может быть преобразовано в значение типа prvalue.
-
все операции в c++ приведут к последовательности фундаментальных операций со встроенным значением. Большинство из этих операций применяются к значению (prvalue), а не к объекту. Перед выполнением этих операций применяется значение lvalue-to-rvalue [expr] Всякий раз, когда выражение glvalue появляется в качестве операнда оператора, ожидающего значение prvalue для этого операнда, значение lvalue-to-rvalue, [...]
-
результаты этих встроенных операций, которые работают со значением, всегда являются prvalue (prvalue - это просто значение, не связанное ни с одним объектом). Затем полученное значение можно использовать как операнд другой встроенной операции или инициализировать объект (операция сохранения в памяти нашей машины), [basic.lval]: prvalue - это выражение, оценка которого инициализирует объект или битовое поле или вычисляет значение операнда оператора, как определено контекстом, в котором оно появляется. Таким образом, в нашем машинном представлении акт сохранения значения в объекте является хранилищем.
Чтобы проиллюстрировать это, давайте проанализируем этот простой фрагмент кода:
int main(int argc, char* argv[]){
int j = 2*argc+1;
}
-
2*argc
Встроенный оператор 2*argc
* вызывается с двумя аргументами 2
и argc
. argc
является lvalue, поэтому применяется значение lvalue-to-rvalue. Значение argc
загружается в регистр процессора (2 может быть немедленным) и выполняется операция multiply
. - результат
2*argc
- это значение, которое непосредственно используется в качестве первого операнда (2*argc)+(argc)
. Затем результирующее значение этой последней операции используется для инициализации объекта j
: результирующее значение сохраняется в представлении памяти j
.
Ответ 3
Значение - это понятие; объект - это вещь на всю жизнь. Это различие имеет тенденцию быть намного более важным для типов классов со сложными конструкторами, но правила применяются в равной степени ко всем типам.
Рассмотрим эту простую программу:
std::string foo() { return std::string{"Hello"}; }
int main() {
std::string f = foo();
}
foo
не создает объект. Создание объекта потребовало бы вызова конструктора класса для начала жизни объекта. Для std::string
это, вероятно, потребует выделения памяти и копирования символов, и по довольно очевидным причинам мы бы хотели избежать этого слишком много раз.
Вместо этого foo
возвращает значение. Он возвращает концепцию "строки, инициализированной символами" Hello "". В конце концов, main
может взять эту абстрактную концепцию и создать объект для представления этого значения. Из-за этого различия создается только один объект, поэтому дополнительные затраты на начало и конец времени жизни объекта должны быть оплачены только один раз.