1

Тема: Типи виразів у С++

Вітаю! По ходу свого вивчення С++ зіткнувся з виразами, зокрема новими, які вроді появилися відносно не давно (rvalue, glvalue, prvalue, xvalue, lvalue). Lvalue начебто просто - все, від чого можна брати адрес. А ось різницю між xvalue і prvalue я до кінця так і не зрозумів, як визначити, що є що? Як наступний код з lvalue робить xvalue і чому це xvalue, а не prvalue?

#include <iostream>
using namespace std;

struct Cat 
{
    
};

int main() 
{
    Cat&& cat = Cat();
    cout << &(static_cast<Cat&&>(cat)); // Помилка
    cout << &cat; // Все ок
}

Що зробив static_cast? По суті він же привів тип T до типу T? Що він змінив?
Ще питання, навіщо потрібно поділ на rvalue і glvalue?
І чому від rvalue reference можна брати адрес? Це хіба не тимчасовий об'єкт?
Якщо щось наплутав, будь ласка, поправте.

Подякували: leofun011

2

Re: Типи виразів у С++

mimik написав:

Ще питання, навіщо потрібно поділ на rvalue і glvalue?

До C++11 було все просто: є lvalues і rvalues, які відрізняються тим, з якого боку оператора присвоювання вони можуть стояти. lvalue може неявно перетворюватися на rvalue, але не навпаки.
В C++11 запровадили семантику переміщення: особливий стан об'єкта, коли його ресурси можна перебирати, бо більше ним не будуть користуватися. Це і є xvalue. За новим визначенням, ліворуч від = може бути glvalue = lvalue+xvalue, праворуч rvalue = prvalue+xvalue.
Вам тут не потрібен xvalue, бо Cat не володіє жодними ресурсами.

mimik написав:

А ось різницю між xvalue і prvalue я до кінця так і не зрозумів, як визначити, що є що?

Якщо воно ще може трохи пожити, то це xvalue. Якщо здохне прямо в цьому виразі - prvalue.

mimik написав:

чому це xvalue, а не prvalue?

Бо && - це xvalue. Так, це зветься "rvalue reference", але rvalue=prvalue+xvalue. Оскільки тут ми маємо не prvalue, то це xvalue.

mimik написав:

Що зробив static_cast? По суті він же привів тип T до типу T? Що він змінив?

Категорію значення з lvalue на xvalue.

mimik написав:

І чому від rvalue reference можна брати адрес? Це хіба не тимчасовий об'єкт?

Reference - це, по суті, і є адреса. Так, це тимчасовий об'єкт - але він тимчасовий у зовнішньому просторі імен. Поки живе rvalue reference, житиме і його адреса. Зате можна виходити з того, що після того, як ми його "відпустимо", він помре, і грабувати його ресурси.

Подякували: leofun01, mimik2

3 Востаннє редагувалося wander (13.06.2020 22:48:18)

Re: Типи виразів у С++

mimik написав:

Що зробив static_cast? По суті він же привів тип T до типу T? Що він змінив?

Він змінив категорію виразу:

http://eel.is/c++draft/expr.static.cast#1 написав:

The result of the expression static_cast<T>(v) is the result of converting the expression v to type T. [...] if T is an rvalue reference to object type, the result is an xvalue; [...]

mimik написав:

І чому від rvalue reference можна брати адрес? Це хіба не тимчасовий об'єкт?

Ні. Ваша змінна cat — lvalue, а її тип rvalue-reference. Тобто грубо кажучи це rvalue, а не prvalue, де останній є реально тимчасовим об'єктом. В даному контексті їх легко розрізнити rvalue — об'єкт з іменем, а prvalue — без. Хоча, якщо говорити більш правильно, то об'єкт таки тимчасовий Cat(), проте в результаті перед тим як померти його врятували та продовжили йому життя шляхом присвоєння до rvalue-reference. Бо формально тимчасовий об'єкт чи ні визначається способом, яким він створювався. Прибиття посилання до тимчасового об'єкта не робить його не тимчасовим.

koala написав:

rvalue=prvalue+xvalue

rvalue = prvalue or an xvalue

Подякували: leofun01, mimik2

4

Re: Типи виразів у С++

koala написав:

До C++11 було все просто: є lvalues і rvalues, які відрізняються тим, з якого боку оператора присвоювання вони можуть стояти. lvalue може неявно перетворюватися на rvalue, але не навпаки.
В C++11 запровадили семантику переміщення: особливий стан об'єкта, коли його ресурси можна перебирати, бо більше ним не будуть користуватися. Це і є xvalue. За новим визначенням, ліворуч від = може бути glvalue = lvalue+xvalue, праворуч rvalue = prvalue+xvalue.

Так, це я загалом зрозумів, як воно виникло.

koala написав:

Reference - це, по суті, і є адреса. Так, це тимчасовий об'єкт - але він тимчасовий у зовнішньому просторі імен. Поки живе rvalue reference, житиме і його адреса. Зате можна виходити з того, що після того, як ми його "відпустимо", він помре, і грабувати його ресурси.

То у тимчасового об'єкта, можна взяти його адресу?

wander написав:

Ні. Ваша змінна cat — lvalue, а її тип rvalue-reference. Тобто грубо кажучи це rvalue, а не prvalue, де останній є реально тимчасовим об'єктом. В даному контексті їх легко розрізнити rvalue — об'єкт з іменем, а prvalue — без. Хоча, якщо говорити більш правильно, то об'єкт таки тимчасовий Cat(), проте в результаті перед тим як померти його врятували та продовжили йому життя шляхом присвоєння до rvalue-reference. Бо формально тимчасовий об'єкт чи ні визначається способом, яким він створювався. Прибиття посилання до тимчасового об'єкта не робить його не тимчасовим.

То вона rvalue чи lvalue?

5 Востаннє редагувалося wander (14.06.2020 00:08:46)

Re: Типи виразів у С++

mimik написав:

То у тимчасового об'єкта, можна взяти його адресу?

Ні, у тимчасового об'єкта не можна взяти адресу, бо на те він і тимчасовий, що невідомо коли помре, та й загалом, нема гарантій, що у нього буде взагалі адрес :)
Також для ясності скажу, що у rvalue = { prvalue | xvalue } теж не можна взяти адресу, бо rvalue — це просто категорія виразу (expression), а не об'єкт. Отже, &cat не дасть вам адресу rvalue. Ви можете отримати лише адрес об'єкта, на який посилається id-вираз cat, а вже цей id-вираз і є lvalue.

mimik написав:

То вона rvalue чи lvalue?

І так, ще раз: id-вираз в оригіналі id-expression cat (тобто ім'я cat) — це lvalue, навіть якщо він посилається на змінну, яка має тип rvalue-reference на Cat.

Подякували: mimik, koala, leofun013

6

Re: Типи виразів у С++

Дякую. Цей момент мені більш-менш зрозумілий, єдине я все ще не розумію, для чого потрібні xvalue?

7 Востаннє редагувалося koala (14.06.2020 00:08:22)

Re: Типи виразів у С++

mimik написав:

Дякую. Цей момент мені більш-менш зрозумілий, єдине я все ще не розумію, для чого потрібні xvalue?

У ККУ для цього є стаття 432 Мародерство :)
Простий приклад. Є тип std::string. Він складається з посилання на масив символів і пари характеристик цього масиву.

std::string a("ABCD");

Ми можемо створити його копію:

std::string b(a);

Конструктор приймає const std::string&, виділяє пам'ять, копіює туди вміст масиву, переносить решту полів. Поки все нормально.

std::string c(b.substring(1,3));

Якщо ви викличемо std::string::substring, то створиться новий string, виділиться пам'ять і т.д. Але потім ми створюємо новий об'єкт, в якому виділяємо пам'ять, копіюємо туди вміст старої пам'яті цього substring, а ту пам'ять звільняємо. Виходить, що виділення і копіювання виконуються двічі. Якби ж конструктор знав, коли йому передають реальний string, а коли тимчасовий... Вже бачите? Я дуже сильно спрощую, але

std::string::string(std::string &&string_xvalue)
{
    this->ptr = string_xvalue.ptr; //вкрали пам'ять
    string_xvalue.ptr = nullptr; //а xvalue поставили вказівник у ніщо
    //...копіюємо решту полів
}

І нас тепер не хвилює, що хтось може скористатися тим, що нам передали в функцію: не може, бо передали xvalue. Він помре, щойно ми його "відпустимо". Або не помре, але це відповідальність програміста, який пхав у нашу функцію свій об'єкт.

Усе це зветься "семантика переміщення", і дуже раджу почитати щось на цю тему.

Подякували: wander, leofun01, mimik3