Каков пример различия в разрешенном использовании или поведении между объектами xvalue и prvalue FOR NON-POD?
Что такое rvalues, lvalues, xvalues, glvalues и prvalues? дает хороший обзор таксономии rvalues /lvalues и один из недавних ответов на этот вопрос вопрос (qaru.site/info/2130/...) подчеркивает, что prvalues являются "похожими" на старые значения r, а новые значения x допускают поведение "lvalue-like".
Однако рассмотрим следующий код:
class X {};
X foo() { return X(); }
int main()
{
foo() = X(); // foo() is a prvalue that successfully appears on the lhs
}
В этом примере выражение foo()
является значением prvalue, которое появляется в левой части, и принимает назначение.
Это заставило меня задуматься - логика, что "xvalues" отличается от "prvalues", потому что xvalues (glvalues, что они есть) могут появляться с левой стороны, кажется, что этот пример нарушен. Здесь мы имеем prvalue - который не является glvalue - успешно появляется на lhs и принимает назначение.
(Примечание: в случае POD приведенный выше пример не будет компилироваться, поэтому для POD различие между значениями x и prvalues, по-видимому, имеет смысл. Поэтому этот вопрос конкретно относится к типам, отличным от POD.)
Что же представляет собой истинное различие в разрешенном использовании или поведении между значением xvalue и значением prvalue, что требует, чтобы это различие записывалось в стандарт? Единственный пример разницы - прекрасный альтернативный ответ.
ДОПОЛНЕНИЕ
Комментарий Pubby был верным. Время жизни prvalue расширяется компилятором, но время жизни xvalue не является.
Итак, вот ответ на вопрос:
Рассмотрим следующий код:
// ***
// Answer to question, from Pubby comment
// ***
class X
{
public:
X() : x(5) {}
int x;
};
X foo() { return X(); }
X&& goo() { return std::move(X()); } // terrible coding, but makes the point
int main()
{
foo() = X();
X&& x1 = foo(); // prvalue - lifetime extended! Object resides directly on stack as return value
X&& x2 = goo(); // xvalue - lifetime not extended. Object (possibly polymorphic) resides somewhere else.
x1.x = 6;
x2.x = 7; // Danger!
std::cout << x1.x << std::endl; // Just fine
std::cout << x2.x << std::endl; // prints garbage in VS 2012
}
Это демонстрирует разницу в поведении между значением prvalue и значением x. Здесь у нас есть идентичный клиентский код, за исключением разницы в привязке (prvalue vs. xvalue).
Как показывает пример кода, время жизни prvalue автоматически расширяется, но время жизни значения x не равно.
Также выявляются и другие очевидные различия: для prvalue сам объект появляется в стеке как возвращаемое значение функции; соответственно, поскольку статический тип prvalue гарантированно является его динамическим типом (см. ответ ниже), продление его времени жизни имеет смысл и может быть выполнено компилятором.
С другой стороны, для значения x объект находится в некотором неизвестном произвольном месте, поэтому компилятор не может легко продлить свое время жизни, особенно учитывая, что тип может быть полиморфным.
Спасибо за ответ.
Ответы
Ответ 1
Для полиморфных выражений xvalue типа nonpod динамический тип выражения обычно неизвестен во время компиляции (поэтому оценивается выражение typeid на них, а вызовы виртуальных функций вообще не могут быть девиртуализированы).
Для prvalues это не применяется. Динамический тип равен статическому типу.
Другое отличие состоит в том, что decltype(e)
является ссылочным типом rvalue для значений x и не ссылочным типом для prvalues.
Еще одно отличие заключается в том, что преобразование lvalue в rvalue не выполняется для prvalues (они уже будут результатом результата). Это можно наблюдать некоторым довольно странным кодом
struct A {
int makeItANonPod;
A() = default;
private:
int andNonStdLayout;
A(A const&) = default;
};
void f(...);
int main() {
f(A()); // OK
f((A&&)A()); // illformed
}
Ответ 2
Какова истинная разница между значением xvalue и значением prvalue? Значение x - это вид rvalue, который может быть cv-квалифицированным и ссылаться на объект и иметь динамический тип, равный или неравный статический тип.
const int&& foo();
int&& _v=foo();
Без значения xval возвращаемое значение функции foo может быть только rvalue. Но встроенные типы не имеют константы! Таким образом, вышеупомянутая неконстантная переменная _v всегда может связывать возвращаемое значение foo(), даже если мы хотим, чтобы foo() возвращал константу rvalue.