Что такое rvalues, lvalues, xvalues, glvalues и prvalues? дает хороший обзор таксономии rvalues / lvalues и один из недавних ответов на этот вопрос (https://stackoverflow.com/a/9552880/368896) подчеркивает, что prvalues »похожи» на значения r в старом стиле, тогда как новые xvalues допускают поведение типа lvalue.
Однако рассмотрим следующий код:
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 различие между xvalues и prvalues, похоже, имеет смысл. Поэтому этот вопрос конкретно касается типов, не относящихся к POD.)
Какова же тогда истинная разница в разрешенном использовании или поведении между xvalue и prvalue, что требует, чтобы это различие было записано в стандарте? Единственным примером различий будет хороший альтернативный ответ.
ДОПОЛНЕНИЕ
Комментарий Пабби был верным. Время жизни prvalue увеличивается компилятором, но время жизни xvalue — нет.
Итак, вот ответ на вопрос:
Рассмотрим следующий код:
// ***
// Answer to question, from Pubby's 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 и xvalue. Здесь мы имеем идентичный клиентский код, за исключением различий в привязке (prvalue против xvalue).
Как показывает пример кода, время жизни значения prvalue автоматически увеличивается, а время жизни значения xvalue — нет.
Выявлены и другие очевидные различия: для prvalue сам объект отображается в стеке как возвращаемое значение функции; соответственно, поскольку статический тип prvalue гарантированно является его динамическим типом (см. ответ ниже), продление его времени жизни имеет смысл и может быть выполнено компилятором.
С другой стороны, для xvalue объект находится в каком-то неизвестном произвольном месте, поэтому компилятор не может легко продлить его время жизни, особенно учитывая, что тип может быть полиморфным.
Спасибо за ответ.
Для выражений xvalue полиморфного типа не под-типа динамический тип выражения обычно неизвестен во время компиляции (поэтому вычисляется выражение typeid для них, и вызовы виртуальных функций в общем случае не могут быть девиртуализированы).
Для prvalues это не относится. Динамический тип равен статическому типу.
Другое отличие состоит в том, что decltype(e)
является ссылочным типом rvalue для значений xvalue и не ссылочным типом для значений prvalue.
Еще одно отличие состоит в том, что преобразование lvalue в rvalue не выполняется для значений prvalue (они уже являются результатом, полученным в результате). Это можно наблюдать по довольно странному коду
struct A {
int makeItANonPod;
A() = default;
private:
int andNonStdLayout;
A(A const&) = default;
};
void f(...);
int main() {
f(A()); // OK
f((A&&)A()); // illformed
}
В чем истинная разница между xvalue и prvalue? Xvalue — это разновидность rvalue, которая может быть cv-квалифицирована и ссылаться на объект и имеют динамический тип, равный или неравный статическому типу.
const int&& foo();
int&& _v=foo();
Без xvalue возвращаемое значение вышеупомянутой функции foo может быть только значением rvalue. Но встроенные типы не имеют постоянного значения! Таким образом, приведенная выше неконстантная переменная _v всегда может связать возвращаемое значение foo (), даже если мы хотим, чтобы foo () возвращала константное значение.