🛠 Основы Move semantics в C

🛠 Основы Move semantics в C Расшифровка

Что нужно знать перед прочтением этой статьи?

Предполагается, что читатель знаком с концепцией ссылок в C , классов, конструкторов, конструкторов копирования, переопределённых операторов и операторов копирования, а также правилом трёх.

Основные формы глагола move

У правильного глагола to move следующие формы:

ferb forms move

Что такое move semantics и когда она имеет место

Добавим в наш класс X конструктор по умолчанию, конструктор и оператор копирования, а также объявление указателя на int.

Листинг 2
X() { resource = new int[100];
}
X(const X& x) { for (int i = 0; i < 100; i) resource[i] = x.resource[i];
}
X& operator=(const X& x) { X copy(x); std::swap(resource, copy.resource); return *this;
}
~X() { delete[] resource;
}
private: int* resource = nullptr;

resource здесь – это какие-то данные, которые, с точки зрения производительности, тяжело и долго копируются и лишнего копирования которых стоит избегать.

Заменим main из листинга 1 на следующий:

Листинг 3
int main() { X x; x = someFunctionReturningX(); //Вызов оператора копирования, внутри которого создаётся копия временного объекта
}

Заметили, да? Мы копируем содержимое временного объекта, в то время как копирования фактически можно избежать, просто забрав (переместив) ресурс из временного объекта, т.к. этот объект всё равно очень скоро (после выхода из конструктора копирования) будет уничтожен и никто не пострадает, если его содержимое станет пустым (или не пустым, но невалидным). Это и есть move semantics.

Важно понять, что move semantics не является способом увеличить производительность каждой строки вашего кода. Move semantics – это механизм, работающий только в определённых случаях. Несмотря на это, он может здорово повысить общую скорость работы вашей программы.

Это всё звучит привлекательно, но как это реализовать? Очень просто, для этого нам понадобятся…

Что такое rvalue и lvalue

Каждое выражение в C характеризуется двумя свойствами: типом и категорией значения (value category[1]). В контексте разбора move semantics нас интересует только последнее.

Стандарт языка определяет три основные категории значений и ещё две составные, которые определяются на основе первых трёх.

Базовыми категориями значений являются lvalue, prvalue и xvalue:

  • lvalue[2] (от left-hand value – значение слева от равно) – фактически всё, чему может быть присвоено значение, например, переменная, результат разыменовывания указателя, ссылка.
  • prvalue[3] (от pure rvalue) – выражение, которое непосредственно инициализирует объект или описывает операнд, например, результат вызова функции, не являющийся ссылкой, результат постфиксных инкремента или декремента, результат арифметической операции.
  • xvalue[4] (от expiring value) – объекты, которые близки к концу времени жизни (lifetime[5]). Фактически xvalue – это анонимные ссылки на rvalue (о ссылках на rvalue – чуть позже), например, результаты вызова функций, возвращающих ссылки на rvalue.

Определив три основные категории значений, можно определить две оставшиеся (составные) – glvalue и rvalue:

Для ясности предлагаем взглянуть на диаграмму Венна:

До C 11 мы имели лишь lvalue и rvalue, а после – rvalue разделили на два вида: xvalue и prvalue, в то время как совокупность xvalue и lvalue стали называть glvalue.

Другие сокращения:  АРМ. А как правильно?) - СЦБИСТ - железнодорожный форум, блоги, фотогалерея, социальная сеть

Грубо говоря, lvalue – всё, чему может быть явно присвоено значение. rvalue – это временные объекты или значения, не связанные ни с какими объектами; что-то витающее в воздухе и ни за чем не закреплённое.

3 формы глагола move, примеры с переводом —

Английский глагол to move [muːv] — правильный или регулярный, поэтому его вторая и третья формы past совпадают и образуются с помощью обычного окончания -ed. Глагол move имеет значения:

двигать, передвигаться, перемещать, шевелить, приводить в движение, делать ход, управлять, манипулировать, переезжаь, переселяться, перевозить, действовать, заставлять, побуждать

Std::move

std::move[11] – это функция из стандартной библиотеки, определённая в хедере <utility>, которая позволяет взять, что угодно (например, lvalue), и сделать из этого rvalue (xvalue, если быть точным).

Круто… И что это нам даёт? Это даёт нам возможность перемещать объекты, rvalue-ссылок на которые у нас нет.

Допустим, наш класс X имеет поле типа std::string. Как реализовать конструктор и оператор перемещения правильно?

Листинг 5
X(X&& x) : stringField(std::move(x.stringField)) noexcept { std::swap(resource, copy.resource);
}
X& operator=(X&& x) noexcept { stringField = std::move(x.stringField); std::swap(resource, copy.resource); return *this;
}

Теперь в конструкторе перемещения для поля типа std::string (stringField) вызывается конструктор перемещения класса std::string, потому что вызов std::move “сделал” из x.

stringField rvalue! В операторе перемещения для stringField вызывается оператор перемещения std::string, потому что вызов std::move “сделал” из x.stringField rvalue.

С точки зрения семантики, обёртка в std::move позволяет отметить какой-либо объект как объект, чьи ресурсы могут быть перемещены.

std::move также активно используется в совокупности с умными указателями (std::unique_ptr), о которых мы тоже писали.

Конструктор и оператор перемещения

С 11 дал нам два инструмента для реализации move semantics в пользовательских классах – конструктор перемещения и оператор перемещения. Это своего рода аналоги конструктора и оператора копирования, но предназначенные не для копирования, а для перемещения. Добавим их в наш класс X:

Листинг 4
X(X&& x) noexcept { std::swap(resource, copy.resource);
}
X& operator=(X&& x) noexcept { std::swap(resource, copy.resource); return *this;
}

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

Теперь при использовании компилятора, поддерживающего C 11, код из листинга 3 больше не будет вызывать оператор копирования, а вместо него будет вызывать оператор перемещения. Почему? Потому что в данном случае справа от знака равно находится rvalue, а конструктор и оператор копирования предназначены для работы именно с rvalue.

Резюмируя последние четыре раздела статьи:

  • C позволяет вашей программе отличать временные объекты от невременных (rvalue от lvalue);
  • позволяет ссылаться на эти временные объекты;
  • в случае, если мы используем их для присваивания или инициализации какого-то другого объекта, C вызывает специальные конструктор либо оператор, в которых мы можем делать, что угодно, например, забирать ресурсы у временного объекта, “ломая” и “портя” его, но избегая при этом потенциально медленного копирования. “Испорченный” временный объект делает то же самое, что сделал бы и не будь он “испорченным”, а именно – уничтожается (на то он и временный).

Обратите внимание на то, что и конструктор и оператор копирования должны быть помечены как noexcept.

Другие сокращения:  Бич – что означает? Определение, значение, примеры употребления

Стоит заметить, правило, известное как правило трёх, становится правилом пяти [9]: если вы реализуете в вашем классе один пункт из следующего списка, вы должны реализовать все пять:

  • конструктор копирования;
  • конструктор перемещения;
  • оператор копирования;
  • оператор перемещения;
  • деструктор.

Внимательный читатель наверняка задался вопросом, что делать, если класс содержит поля не примитивного типа (например, std::string), не являющиеся указателями, ведь в таком случае при вызове std::swap произойдет копирование*. Для таких ситуаций С 11 предлагает нам воспользоваться…

Синонимы глагола to move

  • go
  • shift
  • progress
  • travel
  • be in motion
  • replace
  • transfer

Ссылки на rvalue

Оставив самое сложное позади, поговорим о более близких к практике вещах, о ссылках на rvalue.

При выполнении программы на C постоянно создаются и уничтожаются различного рода временные объекты (rvalue). До C 11 мы не имели возможности сохранить эти объекты для будущего использования, потому что не могли ссылаться на них (вернее, могли, но используя только константные ссылки, а значит, лишаясь возможности изменения).

С приходом C 11 всё изменилось: появилась возможность ссылаться на rvalue (и изменять rvalue через эти ссылки) так же, как мы до этого ссылались на lvalue (кстати говоря, то, что в C мы обычно называем просто ссылками, является на самом деле ссылками на lvalue). Время для примера:

Листинг 1
#include <iostream>
//Подопытный класс
class X {
public: void setA(double a) { //Какой-то сеттер }
};
X someFunctionReturningX() { X x; return x;
}
int main() { // X& xLvalueRef = someFunctionReturningX(); //Не скомпилируется - нельзя привязать rvalue к ссылке на lvalue const X& xConstLvalueRef = someFunctionReturningX(); //xConstLvalueRef.setA(0); //Не скомпилируется X&& xRvalueRef = someFunctionReturningX(); //Привязывание временного объекта к ссылке на rvalue - объект можно менять xRvalueRef.setA(0);
}
  • 1-я строка main, будь она раскомментирована, не скомпилировалась бы, т.к. Стандарт запрещает привязывать временные объекты (rvalue) к ссылкам на lvalue. Однако он разрешает привязывать rvalue к константным ссылкам на lvalue, что и происходит во 2-й строке. Но с константными ссылками есть проблема: они константные! Мы не можем ничего сделать с привязанным rvalue, используя такую ссылку, что показывает 3-я строка.
  • 4-я строка начинается новым синтаксисом – двумя амперсандами (&&), обозначающими объявление ссылки на rvalue [8]. Далее к этой ссылке привязывается rvalue, которое, как видно из 5-й строки, мы можем изменять.

Важно понимать, что сама ссылка на rvalue является lvalue.

Это всё очень хорошо, скажете вы, но как это поможет мне оптимизировать мои программы? Об этом – ниже.

Фразовый глагол move ‹

В целом большая часть значений фразового глагола move очень близка к его основному смысловому содержанию, поэтому многие варианты отличаются лишь какой-либо приставкой, придающей тому или иному значению свой оттенок, требующийся в определенной ситуации. Но существуют и значения, о которых вы вряд ли бы догадались, изучая фразовый глагол move в одиночестве, не участвующий в сочетаниях.

Сложным моментом является тот факт, что у самого глагола move без статуса «фразовый» очень много своих значений, которые способствуют тому, что этот глагол используется в разнообразных контекстах. Например: двигать, развиваться (о событиях), управлять, манипулировать, перейти в другие руки, шевельнуться и т.д. Добавим еще ко всем основным значениям те, которыми обладает фразовый глагол move.

  1. Move about / around – переставлять (мебель), переводить (с должности на должность), переезжать (с места на место), разъезжать, дергаться, вертеться, передвигаться (между объектами)

    We used to move about when I was a child. – Когда я была ребенком, мы переезжали с места на место.

    She can move around with difficulty. – Она с трудом передвигается.

    We moved the wardrobe about. – Мы переставили шкаф.

  2. Move along – предложить кому-то, заставить кого-то пройти (дальше); продвигаться

    Move along! – Проходите!

    Let’s move along. – Пойдем дальше.

  3. Move aside – отодвигаться, отодвинуть

    Could you move this desk aside? – Ты не мог бы отодвинуть этот стол?

  4. Move away – удалять, удалиться, уезжать, отдаляться

    Move your hands away! – Уберите руки!

    She moved away in 2000. – Она уехала в 2000 году.

  5. Move back – отодвинуть, поставить назад, отойти, вернуться (на старое место проживание); сдать назад, попятиться

    I moved the papers back. – Я вернула бумаги на место.

    Have you ever moved back? – Ты когда-нибудь возвращался (на старое место проживания)?

  6. Move in – вводить (войска), поселиться, переехать; посягать на что-либо, приближаться

    He’s moving in next month. – Он поселится в следующем месяце.

    We decided to move in together before we get married. – Мы решили пожить вместе перед тем, как пожениться.

  7. Move off – отходить (трогаться), уходить, уезжать, удалять

    The referee moved him off. – Судья удалил его.

    The train is moving off. – Поезд отходит.

  8. Move on – продвигать, переставлять (вперед), продолжить; уйти в мир иной

    After three days at my mother’s house we decided to move on. – Погостив у мамы три дня, мы решили двигаться дальше.

    She understands that she should move on to a better job. – Она понимает, что ей надо переходить на более подходящую работу.

    We have talked a lot about this, so it’s high time we moved on. – Мы много об этом говорили, поэтому давно пора перейти к другому вопросу.

  9. Move out – выселять, съезжать (с места проживания); расстаться, вывозить

    We have to move out next week. – Нам нужно съехать на следующей неделе.

    He moved his family out of the city. – Он вывез семью из города.

  10. Move over – отодвигать, подвинуть(ся); уступить должность

    Is it too difficult to move over? – Очень трудно подвинуться?

    Would you move over your hand a little? – Не могли бы вы немного отодвинуть руку?

  11. Move up – подвигать, подвинуть; перевести, выйти (в люди); подставить, повышаться (о ценах); продвигаться

    Everyone wants to move up in the world. – Каждый хочет занять более заметное место в обществе.

    What should I do to move up the promotional ladder? – Что мне следует сделать, чтобы продвинуться по служебной лестнице?

    Move up and let me sit down! – Подвиньтесь и дайте мне сесть!

Данная тема тесно связана с другими, описанными в статьях, на которые необходимо обратить внимание:

После ознакомления с ними рекомендуем пройти следующий тест: «Тест #3 на употребление фразовых глаголов в английском языке».

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl Enter.

Вывод

Move semantics проявляется лишь в определённых случаях. Move semantics позволяет забирать ресурсы у временных объектов, которые, как правило, в скором времени будут уничтожены, тем самым избегая лишнего копирования. Основными инструментами языка и стандартной библиотеки для реализации move semantics являются:

Конструктор перемещения вызывается, когда объект инициализируется rvalue, оператор перемещения – когда объекту присваивается rvalue. std::move отмечает объекты, ресурсы которых могут быть перемещены, превращая эти объекты в rvalue (xvalue).

Другие сокращения:  Перевод английских матерных слов на русский язык | Пикабу
Оцените статью
Расшифруй.Ру