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

Обновлено: 17.05.2024

Как только вы начнете использовать семантику перемещения более регулярно, вы начнете сталкиваться со случаями, когда вы хотите использовать семантику перемещения, но объекты, с которыми вам придется работать, являются l-значениями, а не r-значениями. Рассмотрим в качестве примера следующую функцию обмена:

Эта функция, принимающая два объекта типа T (в данном случае std::string ), меняет местами их значения, создавая три копии. Следовательно, эта программа печатает:

Как мы показали в прошлом уроке, копирование может быть неэффективным. И эта версия функции обмена создает 3 копии. Это приводит к слишком большому количеству созданий и уничтожений строк, что происходит довольно медленно.

Однако здесь не нужно выполнять копирование. Всё, что мы действительно пытаемся сделать, это поменять местами значения a и b , что также можно сделать, используя вместо этого 3 перемещения! Поэтому, если мы переключимся с семантики копирования на семантику перемещения, мы сможем сделать наш код более производительным.

Но как? Проблема здесь в том, что параметры a и b являются lvalue-ссылками, а не rvalue-ссылками, поэтому у нас нет способа вызвать конструктор перемещения и оператор присваивания перемещением вместо конструктора копирования и присваивания копированием. По умолчанию мы получаем поведение конструктора копирования и присваивания копированием. Что же делать?

std::move

В C++11 std::move – это функция стандартной библиотеки, которая преобразует (используя static_cast ) свой аргумент в rvalue-ссылку, чтобы можно было использовать семантику перемещения. Таким образом, мы можем использовать std::move , чтобы преобразовать l-значение в тип, который предпочтительнее перемещать, а не копировать. std::move определяется в заголовке utility .

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

Этот код печатает тот же результат, что и выше:

Но это намного эффективнее. Когда tmp инициализируется, вместо создания копии x мы используем std::move для преобразования переменной x из l-значения в r-значение. Поскольку параметр теперь является r-значением, используется семантика перемещения, и x перемещается в tmp .

Еще через еще пару перемещений значение переменной x было перемещено в y , а значение y было перемещено в x .

Еще один пример

Мы также можем использовать std::move при заполнении l-значениями элементов контейнеров, таких как std::vector .

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

Эта программа печатает:

В первом случае мы передали в функцию push_back() l-значение, поэтому для добавления элемента в вектор она использовала семантику копирования. По этой причине значение в str остается без изменений.

Во втором случае мы передали в push_back() r-значение (на самом деле l-значение, преобразованное с помощью std::move ), поэтому для добавления элемента в вектор она использовала семантику перемещения. Это более эффективно, поскольку элемент вектора может забрать значение строки, а не копировать его. В этом случае str остается пустым.

Здесь стоит повторить, что std::move() подсказывает компилятору, что программисту этот объект больше не нужен (по крайней мере, не в его текущем состоянии). Следовательно, вам не следует использовать std::move() для любого постоянного объекта, который вы не хотите изменять, и вы не должны ожидать, что состояние каких-либо объектов, к которым был применен std::move() , будет таким же после того, как они перемещены!

Функции перемещения должны всегда оставлять ваши объекты в четко определенном состоянии

Как мы отметили в предыдущем уроке, рекомендуется всегда оставлять объекты, из которых были забраны ресурсы, в каком-либо четко определенном (детерминированном) состоянии. В идеале это должно быть «нулевое состояние», когда объект возвращается в неинициализированное или нулевое состояние. Теперь мы можем поговорить о том, почему: при использовании std::move «обобранный» объект может оказаться вовсе не временным. Пользователь может захотеть повторно использовать этот (теперь пустой) объект снова или протестировать его каким-либо образом и сделать что-то в соответствии с результатами теста.

В приведенном выше примере строка str после перемещения устанавливается в пустую строку (что std::string всегда делает после успешного перемещения). Это позволяет нам повторно использовать переменную str , если захотим (или мы можем игнорировать ее, если она нам больше не нужна).

Где еще полезен std::move ?

std::move также может быть полезен при сортировке массива элементов. Многие алгоритмы сортировки (например, сортировка выбором и пузырьковая сортировка) работают путем обмена местами пар элементов. В предыдущих уроках для выполнения обмена местами нам приходилось прибегать к семантике копирования. Теперь мы можем использовать семантику перемещения, которая более эффективна.

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

Заключение

std::move можно использовать везде, где мы хотим обрабатывать l-значение как r-значение, чтобы использовать семантику перемещения вместо семантики копирования.

В этом разделе описывается, как написать конструктор перемещения и оператор присваивания перемещения для класса C++. Конструктор перемещения позволяет перемещать ресурсы, принадлежащие объекту rvalue, в lvalue без копирования. Дополнительные сведения о семантике перемещения см. в описании декларатора ссылки Rvalue: &&.

Этот раздел построен на основе приведенного ниже класса C++ MemoryBlock , который управляет буфером памяти.

В следующих процедурах описывается создание конструктора перемещения и оператора присваивания перемещения для этого примера класса C++.

Создание конструктора перемещения для класса C++

Определите пустой метод конструктора, принимающий в качестве параметра ссылку rvalue на тип класса, как показано в следующем примере:

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

Присвойте данным-членам исходного объекта значения по умолчанию. Это не позволяет деструктору многократно освобождать ресурсы (например, память):

Создание оператора присваивания перемещения для класса C++

Определите пустой оператор присваивания, принимающий в качестве параметра ссылку rvalue на тип класса и возвращающий ссылку на тип класса, как показано в следующем примере:

В операторе присваивания перемещения добавьте условный оператор, который не выполняет никакой операции при попытке присвоить объект самому себе.

В условном операторе освободите все ресурсы (такие как память) из объекта, которому производится присваивание.

В следующем примере освобождается член _data из объекта, которому производится присваивание:

Выполните шаги 2 и 3 из первой процедуры, чтобы переместить данные-члены из исходного объекта в создаваемый объект:

Верните ссылку на текущий объект, как показано в следующем примере:

Пример. Полный конструктор перемещения и оператор присваивания

В следующем примере показаны полные конструктор перемещения и оператор назначения перемещения для класса MemoryBlock :

Пример использования семантики перемещения для повышения производительности

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

В этом примере выводятся следующие данные:

До Visual Studio 2010 г. в этом примере выводятся следующие выходные данные:

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

Отказоустойчивость

Во избежание утечки ресурсов (таких как память, дескрипторы файлов и сокеты) обязательно освобождайте их в операторе присваивания перемещения.

Чтобы предотвратить невосстановимое уничтожение ресурсов, в операторе присваивания перемещения необходимо правильно обрабатывать присваивания самому себе.

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

Шаблон класса move_iterator является программой-оболочкой для итератора. Move_iterator обеспечивает такое же поведение, как итератор, который он упаковывает (хранит), за исключением того, что оператор деreference хранимого итератора преобразуется в ссылку rvalue, превратив копию в перемещение. Дополнительные сведения о rvalue см. в статье Rvalue Reference Declarator: &&.

Синтаксис

Remarks

Шаблон класса описывает объект, который ведет себя как итератор, за исключением случаев, когда разыменовывается. Он хранит итератор произвольного доступа типа Iterator , доступный посредством функции-члена base() . Все операции с итератором move_iterator выполняются непосредственно в сохраненном итераторе, за исключением того, что результат operator* явным образом приводится к value_type&& для создания ссылки rvalue.

Может move_iterator быть способен выполнять операции, которые не определены итератором оболочки. Эти операции не следует использовать.

Конструкторы

Конструктор Описание
move_iterator Конструктор для объектов типа move_iterator .

Определения типов

Имя типа Описание
iterator_type Синоним параметра шаблона RandomIterator .
iterator_category Синоним более длинного выражения typename с таким же именем, iterator_category определяет общие возможности данного итератора.
Value_type Синоним более длинного выражения typename с таким же именем, value_type описывает тип элементов итератора.
difference_type Синоним более длинного выражения typename с таким же именем, difference_type описывает целочисленный тип, необходимый для выражения разницы между элементами.
Указатель Синоним параметра шаблона RandomIterator .
reference Синоним ссылки rvalue value_type&& .

Функции элементов

Функция-член Описание
base Функция-член возвращает сохраненный итератор, инкапсулированный данным итератором move_iterator .

Операторы

Оператор Описание
move_iterator::operator* Возвращает (reference)*base(). .
move_iterator::operator++ Увеличивает значение сохраненного итератора. Точное поведение зависит от того, является ли это операция предварительного увеличения или после добавочного увеличения.
move_iterator::operator-- Уменьшает значение сохраненного итератора. Точное поведение зависит от того, является ли она предварительной или постдескриментной операцией.
move_iterator::operator-> Возвращает &**this .
move_iterator::operator- Возвращает move_iterator(*this) -= путем вычитания правого значения из текущей позиции.
move_iterator::operator[] Возвращает (reference)*(*this + off) . Позволяет указать смещение с текущей базы для получения значения в этом местоположении.
move_iterator::operator+ Возвращает move_iterator(*this) += значение. Позволяет добавить смещение в текущую базу для получения значения в этом местоположении.
move_iterator::operator+= Добавляет правое значение к сохраненному итератору и возвращает *this .
move_iterator::operator-= Вычитает правое значение из сохраненного итератора и возвращает *this .

Требования

Заголовка:

Пространство имен: std

move_iterator::base

Возвращает сохраненный итератор для этого move_iterator .

Remarks

Эта функция-член возвращает сохраненный итератор.

move_iterator::difference_type

Тип difference_type является move_iterator typedef на основе признака итератора difference_type и может использоваться с ним взаимозаменяемым образом.

Remarks

Этот тип — синоним для признака итератора typename iterator_traits::pointer .

move_iterator::iterator_category

Тип iterator_category является move_iterator typedef на основе признака итератора iterator_category и может использоваться с ним взаимозаменяемым образом.

Remarks

Этот тип — синоним для признака итератора typename iterator_traits::iterator_category .

move_iterator::iterator_type

Тип iterator_type основан на параметре шаблона RandomIterator для шаблона класса move_iterator и может использоваться вместо него.

Remarks

Этот тип является синонимом для параметра шаблона RandomIterator .

move_iterator::move_iterator

Создает итератор перемещения. Использует параметр в качестве сохраненного итератора.

Параметры

right
Итератор, который требуется использовать в качестве сохраненного итератора.

Remarks

Первый конструктор инициализирует сохраненный итератор с помощью его конструктора по умолчанию. Остальные конструкторы инициализируют сохраненный итератор с использованием base.base() .

move_iterator::operator+=

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

Параметры

_Off
Смещение, добавляемое к текущей позиции, для определения новой текущей позиции.

Возвращаемое значение

Возвращает новый текущий элемент.

Remarks

Оператор добавляет _Off в сохраненный итератор. Затем возвращает *this .

move_iterator::operator-=

Выполняет переход через заданное число предыдущих элементов. Этот оператор вычитает смещение из сохраненного итератора.

Параметры

Remarks

Оператор вычисляет *this += -_Off . Затем возвращает *this .

move_iterator::operator++

Увеличивает сохраненный итератор, принадлежащий этому move_iterator . Доступ к текущему элементу осуществляется оператором postincrement. Доступ к следующему элементу осуществляется префиксным оператором.

Параметры

Remarks

Первый (префиксный) оператор увеличивает сохраненный итератор. Затем возвращает *this .

Второй (постфиксный) оператор создает копию *this и вычисляет ++*this . Затем возвращает эту копию.

move_iterator::operator+

Возвращает позицию итератора, увеличенную на любое число элементов.

Параметры

Remarks

Оператор возвращает move_iterator(*this) += _Off .

move_iterator::operator[]

Разрешает доступ индексу массива к элементам во всем диапазоне move iterator .

Параметры

Remarks

Оператор возвращает (reference)*(*this + _Off) .

move_iterator::operator--

Предекрементные и постдекрементные операторы-члены уменьшают сохраненный итератор на единицу.

Параметры

Remarks

Первый оператор-член (предекрементный) уменьшает сохраненный итератор. Затем возвращает *this .

Второй (постдекрементный) оператор создает копию *this и вычисляет --*this . Затем возвращает эту копию.

move_iterator::operator-

Уменьшает значение сохраненного итератора и возвращает указанное значение.

Параметры

Remarks

Оператор возвращает move_iterator(*this) -= _Off .

move_iterator::operator*

Разыменовывает сохраненный итератор и возвращает значение. Оператор похож на rvalue reference и выполняет присваивание с перемещением. Оператор передает текущий элемент из базового итератора. Следующий элемент становится текущим.

Remarks

Оператор возвращает (reference)*base() .

move_iterator::operator->

Как и обычный RandomIterator operator-> , он предоставляет доступ к полям, которые относятся к текущему элементу.

Remarks

Оператор возвращает &**this .

move_iterator::pointer

Тип pointer — это typedef , основанный на произвольном итераторе RandomIterator для move_iterator , и может использоваться с ним взаимозаменяемым образом.

Remarks

Тип является синонимом RandomIterator .

move_iterator::reference

Тип reference — typedef на основе value_type&& для move_iterator , его можно использовать вместо value_type&& .

Remarks

Этот тип является синонимом value_type&& , который представляет собой ссылку rvalue.

move_iterator::value_type

Тип value_type является move_iterator typedef на основе признака итератора value_type и может использоваться с ним взаимозаменяемым образом.

Remarks

Этот тип — синоним для признака итератора typename iterator_traits::value_type .

В этом разделе описывается, как написать конструктор перемещения и оператор присваивания перемещения для класса C++. Конструктор перемещения позволяет перемещать ресурсы, принадлежащие объекту rvalue, в lvalue без копирования. Дополнительные сведения о семантике перемещения см. в описании декларатора ссылки Rvalue: &&.

Этот раздел построен на основе приведенного ниже класса C++ MemoryBlock , который управляет буфером памяти.

В следующих процедурах описывается создание конструктора перемещения и оператора присваивания перемещения для этого примера класса C++.

Создание конструктора перемещения для класса C++

Определите пустой метод конструктора, принимающий в качестве параметра ссылку rvalue на тип класса, как показано в следующем примере:

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

Присвойте данным-членам исходного объекта значения по умолчанию. Это не позволяет деструктору многократно освобождать ресурсы (например, память):

Создание оператора присваивания перемещения для класса C++

Определите пустой оператор присваивания, принимающий в качестве параметра ссылку rvalue на тип класса и возвращающий ссылку на тип класса, как показано в следующем примере:

В операторе присваивания перемещения добавьте условный оператор, который не выполняет никакой операции при попытке присвоить объект самому себе.

В условном операторе освободите все ресурсы (такие как память) из объекта, которому производится присваивание.

В следующем примере освобождается член _data из объекта, которому производится присваивание:

Выполните шаги 2 и 3 из первой процедуры, чтобы переместить данные-члены из исходного объекта в создаваемый объект:

Верните ссылку на текущий объект, как показано в следующем примере:

Пример. Полный конструктор перемещения и оператор присваивания

В следующем примере показаны полные конструктор перемещения и оператор назначения перемещения для класса MemoryBlock :

Пример использования семантики перемещения для повышения производительности

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

В этом примере выводятся следующие данные:

До Visual Studio 2010 г. в этом примере выводятся следующие выходные данные:

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

Отказоустойчивость

Во избежание утечки ресурсов (таких как память, дескрипторы файлов и сокеты) обязательно освобождайте их в операторе присваивания перемещения.

Чтобы предотвратить невосстановимое уничтожение ресурсов, в операторе присваивания перемещения необходимо правильно обрабатывать присваивания самому себе.

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

В уроке «M.1 – Введение в умные указатели и семантику перемещения» мы рассмотрели std::auto_ptr , обсудили необходимость семантики перемещения и рассмотрели некоторые недостатки, которые возникают, когда функции, разработанные для семантики копирования (конструкторы копирования и операторы присваивания копированием) переопределяются для реализации семантики перемещения.

В этом уроке мы более подробно рассмотрим, как C++11 решает эти проблемы с помощью конструкторов перемещения и присваивания перемещением.

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

Во-первых, давайте сделаем обзор семантики копирования.

Конструкторы копирования используются для инициализации класса путем создания копии объекта того же класса. Присваивание копированием используется для копирования одного объекта класса в другой существующий объект класса. По умолчанию, если конструктор копирования и оператор присваивания копированием не указаны явно, C++ предоставляет их. Эти предоставляемые компилятором функции создают поверхностные копии, что может вызывать проблемы для классов, динамически выделяющих память. Таким образом, классы, которые имеют дело с динамической памятью, должны переопределять эти функции для создания глубоких копий.

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

В этой программе мы используем функцию с именем generateResource() для создания умного указателя, инкапсулирующего ресурс, который затем передается обратно в функцию main() . Затем функция main() присваивает его существующему объекту Auto_ptr3 .

Когда эта программа запускается, она печатает:

Для такой простой программы происходит слишком много созданий и уничтожений объектов Resource ! Что тут происходит?

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

Неэффективно, но, по крайней мере, не дает сбоев!

Однако с семантикой перемещения мы можем добиться большего.

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

C++11 определяет две новые функции, обслуживающие семантику перемещения: конструктор перемещения и оператор присваивания перемещением. В то время как цель конструктора копирования и присваивания копированием – выполнить копирование одного объекта в другой, цель конструктора перемещения и присваивания перемещением – передать владение ресурсами от одного объекта к другому (что обычно намного дешевле, чем создание копии).

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

Вот тот же класс Auto_ptr3 , что и выше, с добавленными конструктором перемещения и оператором присваивания перемещением. Для сравнения мы оставили выполняющие глубокое копирование конструктор копирования и оператор присваивания копированием.

Конструктор перемещения и оператор присваивания перемещением просты. Вместо того, чтобы выполнять глубокое копирование исходного объект ( а ) в неявный объект this , мы просто перемещаем (крадем) ресурсы исходного объекта. Это включает в себя поверхностное копирование указателя исходного объекта в неявный объект this с последующей установкой для указателя исходного объекта значения nullptr .

При запуске эта программа печатает:

Это намного лучше!

Ход программы точно такой же, как и раньше. Однако вместо вызова конструктора копирования и оператора присваивания копированием эта программа вызывает конструктор перемещения и оператор присваивания перемещением. Рассмотрим немного подробнее:

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

Когда вызываются конструктор перемещения и присваивание перемещением?

Конструктор перемещения и присваивание перемещением вызываются, когда эти функции определены, а аргументом для построения или присваивания является r-значение. Чаще всего это r-значение будет литералом или временным значением.

В большинстве случаев конструктор перемещения и оператор присваивания перемещением не предоставляются по умолчанию, если в классе нет определенных конструкторов копирования, присваивания копированием, присваивания перемещением или деструкторов. Однако дефолтные конструктор перемещения и присваивание перемещением делают то же самое, что и дефолтные конструктор копирования и присваивание копированием (делать копии, а не перемещают).

Правило

Если вам нужен конструктор перемещения и присваивание перемещением, выполняющее перемещения, вам нужно будет написать их самостоятельно.

Ключевой момент в семантике перемещения

Теперь у вас достаточно контекста для понимания ключевой идеи семантики перемещения.

Если мы создаем объект или выполняем присваивание, в котором аргументом является l-значение, единственное разумное, что мы можем сделать, – это скопировать l-значение. Мы не можем предположить, что изменение l-значения безопасно, потому что позже в программе оно может быть снова использовано. Если у нас есть выражение a = b , мы не можем ожидать каких-либо изменений b .

Однако, если мы создаем объект или выполняем присваивание, в котором аргументом является r-значение, тогда мы знаем, что r-значение – это всего лишь временный объект какого-то типа. Вместо того, чтобы копировать его (что может быть дорогостоящим), мы можем просто передать его ресурсы (что дешево) объекту, который мы создаем или которому выполняем присваивание. Это безопасно, потому что временный объект в любом случае будет уничтожен в конце выражения, поэтому мы знаем, что он больше никогда не будет использоваться!

C++11, через rvalue-ссылки, дает нам возможность обеспечивать различное поведение, когда аргументом является r-значение или l-значение, что позволяет нам принимать более разумные и эффективные решения о том, как должны вести себя наши объекты.

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

В приведенных выше примерах и конструктор перемещения, и функции присваивания перемещением устанавливают a.m_ptr в значение nullptr . Это может показаться лишним – в конце концов, если a – временное r-значение, зачем беспокоиться о выполнении «очистки», если параметр a всё равно будет уничтожен?

Ответ прост: когда a выходит за пределы области видимости, вызывается деструктор a , и a.m_ptr удаляется. Если в этот момент a.m_ptr всё еще указывает на тот же объект, что и m_ptr , тогда m_ptr останется висячим указателем. Когда объект, содержащий m_ptr , в конечном итоге будет использован (или уничтожен), мы получим неопределенное поведение.

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

Автоматические l-значения, возвращаемые по значению, могут быть перемещены вместо копирования

В функции generateResource() в примере выше с Auto_ptr4 , когда переменная res возвращается по значению, она перемещается, а не копируется, даже если res является l-значением. В спецификации C++ есть специальное правило, согласно которому автоматические объекты, возвращаемые функцией по значению, можно перемещать, даже если они являются l-значениями. Это имеет смысл, так как res всё равно будет уничтожен в конце функции! С таким же успехом мы могли бы забрать его ресурсы, вместо того, чтобы выполнять дорогостоящее и ненужное копирование.

Хотя компилятор может перемещать возвращаемые l-значения, в некоторых случаях он может добиться еще большего, просто полностью исключив копирование (что позволяет вовсе избежать необходимости выполнять копирование или перемещение). В таком случае не будут вызываться ни конструктор копирования, ни конструктор перемещения.

Отключение копирования

В приведенном выше классе Auto_ptr 4 мы оставили для сравнения конструктор копирования и оператор присваивания. Но в классах с поддержкой перемещения иногда желательно удалить функции конструктора копирования и присваивания копированием, чтобы гарантировать, что копии не будут созданы. В случае с нашим классом Auto_ptr мы не хотим копировать наш шаблонный объект T – потому что это дорого, и класс T может даже не поддерживать копирование!

Вот версия Auto_ptr , которая поддерживает семантику перемещения, но не поддерживает семантику копирования:

Если бы вы попытались передать функции l-значение Auto_ptr5 по значению, компилятор пожаловался бы, что конструктор копирования, необходимый для инициализации аргумента функции, был удален. Это хорошо, потому что мы, вероятно, всё равно должны передавать Auto_ptr5 по константной lvalue-ссылке!

Auto_ptr5 – это (наконец) хороший класс умных указателей. И на самом деле стандартная библиотека содержит класс, очень похожий на этот (и который вы должны использовать вместо этого), с именем std::unique_ptr . Подробнее об std::unique_ptr мы поговорим в этой главе позже.

Еще один пример

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

Теперь давайте, используем этот класс в программе, чтобы показать, как работает этот класс, когда мы размещаем миллион целых чисел в куче. Мы собираемся использовать класс Timer , который мы разработали в уроке «12.18 – Определение времени выполнения кода». Мы будем использовать его, чтобы измерить скорость выполнения нашего кода и показать вам разницу в производительности между копированием и перемещением.

На одной из машин автора в режиме релиза эта программа выполнилась за 0,00825559 секунды.

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

На той же машине эта программа была выполнена за 0,0056 секунды.

Сравним время выполнения этих двух программ, 0,0056 / 0,00825559 = 67,8%. Версия с перемещением была почти на 33% быстрее!

Читайте также: