Каков пример различий в разрешенном использовании или поведении между xvalue и prvalue для объектов без POD?

Что такое rvalues, lvalues, xvalues, glvalues ​​и prvalues? дает хороший обзор таксономии rvalues ​​/lvalues, и один из недавних ответов на этот вопрос ( /questions/47827257/chto-takoe-rvalues-lvalues-xvalues-glvalues-i-prvalues/47827353#47827353) подчеркивает тот факт, что prvalues ​​"подобны" значениям старого стиля в то время как новые значения xvalue допускают поведение типа 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 различие между значениями xval и 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 объект находится в каком-то неизвестном произвольном месте, поэтому компилятор не может легко продлить его время жизни, особенно учитывая, что тип может быть полиморфным.

Спасибо за ответ.

2 ответа

Решение

Для выражений xvalue полиморфного типа не под-типа динамический тип выражения обычно неизвестен во время компиляции (поэтому вычисляется выражение typeid для них, и вызовы виртуальных функций в общем случае не могут быть девиртуализированы).

Для prvalues ​​это не относится. Динамический тип равен статическому типу.

Другое отличие состоит в том, что decltype(e) является ссылочным типом rvalue для значений xvalue и не ссылочным типом для значений prvalue.

Еще одно отличие состоит в том, что преобразование 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
}

В чем истинная разница между xvalue и prvalue? Значение xvalue является своего рода значением rvalue, которое может быть cv-квалифицированным и относиться к объекту и иметь динамический тип, равный или не равный статическому типу.

const int&& foo();
int&& _v=foo();

Без xvalue возвращаемое значение вышеупомянутой функции foo может быть только значением rvalue. Но встроенные типы не имеют постоянного значения! Таким образом, приведенная выше неконстантная переменная _v всегда может связать возвращаемое значение foo(), даже если мы хотим, чтобы foo () возвращало константное значение.

Другие вопросы по тегам