Категория выражений xvalue



Да кто этот ваш xvalue?! В продолжение к предыдущим постам.



Появление этой категории обусловлено некоторыми издержками копирования, которые свойственны выражениям других категорий.



Как уже было сказано однажды, к категории xvalue относятся временные выражения, ресурс которых можно перераспределить после их уничтожения.



Все это звучит как-то абстрактно, давайте глянем пример:

1. Существует временный объект класса string, который хранит 10 Мб текста на куче.

2. Строчку хотят сохранить в другом объекте, а временный объект удалить.



В прямой постановке задачи, мы как раз оперируем категориями lvalue и rvalue:



std::string nstr = tstr;

// ~~^~~ ~~^~~

// lvalue lvalue -> rvalue



// Then destroy temporary string 'tstr'




Но неужели мы реально будем копировать 10 Мб текста с кучи в другое место, чтобы потом удалить исходные данные? То есть мы сделаем лишний системный вызов на выделение 10 Мб памяти, потом будем посимвольно копировать 10 000 000 байт, а затем мы просто удалим источник?...



По сути, это и есть те накладные расходы, которые тормозят нашу программу. Кажется, что этого можно избежать. Например, можно сказать другому объекту, что теперь он новый владелец данных временного объекта! То есть мы передадим другому объекту указатель на текст и сделаем так, чтобы временный объект его не удалял. Новый объект сможет дальше продолжить пользоваться текстом, возможно, очень долго, когда старый уже исчезнет. Формально, поменяется лишь оболочка над текстом.



Исходя из этой логики пример может быть эффективно решен следующей последовательностью действий:

1. Инициализируем новый объект string, скопировав указатель на текст и счетчики размера из временного объекта.

3. Во временном объекте установим указатель на текст nullptr и занулим счетчики размера строки, чтобы при вызове деструктора наши данные не потёрлись.

4. Разрушим временный объект.

5. Радуемся новому объекту, которых хранит ресурсы временного объекта!



Таким образом, мы сэкономили время на выделении памяти и его копировании, и даже ни в чем не проиграли. Мы можем написать отдельную функцию или метод, который будет выполнять этот алгоритм передачи данных. Однако, удобно ли нам вызывать такую функцию каждый раз? Будет ли этот механизм удобно использовать во всем проекте?



Начиная с C++11 вводится специальная категория выражений для обработки таких временных объектов — xvalue. Так же вводится специальный тип rvalue reference, для которого можно добавить перегрузки операторов и конструкторов:

class string

{

public:

// Constructor for

// rvalue reference of string 'other'

string(string &&other) noexcept

{ ... }



// Assign operator for

// rvalue reference of string 'other'

string& operator=(string &&other) noexcept

{ ... }

};




⚠️ Ранее мы использовали rvalue, как имя категории выражений. Теперь появляется ТИП rvalue reference, который относится к категории выражения xvalue. Не путайтесь, пожалуйста! Я считаю это неудачной терминологией стандарта, которую надо просто запомнить.



Тип rvalue reference задаётся с помощью && перед именем класса. Например:

std::string &&value      = other;

// ~~^~~

// rvalue reference




Теперь каждый класс может определять внутри себя логику передачи владения ресурсом. Таким образом, получилось интегрировать нововведения в действующую языковую модель.



Обратите внимание, как легко и непринужденно тут проявляется идиома RAII. Жизненный цикл объекта остается неизменным и предсказуемым, а ресурсы передаются между объектами: один создал строчку, а другой её удалит.



Будь я на вашем месте, мне бы стало непонятно, как же использовать всю эту лабуду? Продолжение в комментарии!



#cppcore #memory #algorithm