Вызов функции move() указывает компилятору, что имеющееся l-значение следует рассматривать как r-значение. Следует помнить, что приведенный выше вызов функции move() обещает не использовать rr1 ни для чего, кроме присвоения или удаления. После вызова функции move() нельзя сделать никаких предположений о значении уже перемещенного объекта.

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

Как уже упоминалось, для использования большинства имен из библиотеки, включая функцию move() (см. раздел 13.5), не нужно предоставлять объявление using (см. раздел 3.1). Произойдет вызов функции std::move(), а не move(). Причины этого рассматриваются в разделе 18.2.3.

Код, применяющий функцию move(), должен использовать синтаксис std::move(), а не move(). Это позволит избежать возможных конфликтов имен.

Упражнения раздела 13.6.1

Упражнение 13.45. В чем разница между ссылкой на r-значение и ссылкой на l-значение.

Упражнение 13.46. Какой вид ссылки может быть связан со следующими инициализаторами?

int f();

vector vi(100);

int? r1 = f();

int? r2 = vi[0] ;

int? r3 = r1;

int? r4 = vi[0] * f();

Упражнение 13.47. Снабдите конструктором копий и оператором присвоения копии класса String из упражнения 13.44 раздела 13.5, функции которого выводят сообщения при каждом вызове.

Упражнение 13.48. Определите вектор vector и вызовите для него функцию push_back() несколько раз. Запустите программу и посмотрите, как часто копируются строки.

<p><image l:href="#books.png"/>13.6.2. Конструктор перемещения и присваивание при перемещении</p>

Подобно классу string (и другим библиотечным классам), наши собственные классы могут извлечь пользу из способности перемещения ресурсов вместо копирования. Чтобы позволить собственным типам операции перемещения, следует определить конструктор перемещения и оператор присваивания при перемещении. Эти члены подобны соответствующим функциям копирования, но они захватывают ресурсы заданного объекта, а не копируют их.

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

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

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

StrVec::StrVec(StrVec &&s) noexcept // перемещение не будет передавать

                                    // исключений

 // инициализаторы членов получают ресурсы из s

 : elements(s.elements), first_free(s.first_free), cap(s.cap) {

 // оставить s в состоянии, при котором запуск деструктора безопасен

 s.elements = s.first_free = s.cap = nullptr;

}

Оператор noexcept (уведомляющий о том, что конструктор не передает исключений) описан ниже, а пока рассмотрим, что делает этот конструктор.

В отличие от конструктора копий, конструктор перемещения не резервирует новую память; он получает ее от заданного объекта класса StrVec. Получив область памяти от своего аргумента, тело конструктора присваивает указателям заданного объекта значение nullptr. После перемещения оригинальный объект продолжает существовать. В конечном счете оригинальный объект будет удален, а значит, будет выполнен его деструктор. Деструктор класса StrVec вызывает функцию deallocate() для указателя first_free. Если забыть изменить указатель s.first_free, то удаление оригинального объекта освободит область памяти, которая была только что передана.

Операции перемещения, библиотечные контейнеры и исключения
Перейти на страницу:

Похожие книги