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

Обновлено: 06.05.2024

Чтобы настроить, как класс инициализирует его члены или вызывать функции при создании объекта класса, определите конструктор. Конструкторы имеют имена, совпадающие с именами классов, и не имеют возвращаемых значений. Вы можете определить столько перегруженных конструкторов, сколько необходимо для настройки инициализации различными способами. Как правило, конструкторы имеют открытые специальные возможности, чтобы код за пределами определения класса или иерархии наследования может создавать объекты класса. Но вы также можете объявить конструктор как protected или private .

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

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

  • Конструкторы могут быть объявлены как inline , , explicitfriend или constexpr .
  • Конструктор может инициализировать объект, объявленный как const , volatile или const volatile . Объект становится const после завершения конструктора.
  • Чтобы определить конструктор в файле реализации, присвойте ему полное имя, как и любая другая функция-член: Box::Box() .

Списки инициализаторов элементов

При необходимости конструктор может иметь список инициализаторов элементов, который инициализирует члены класса перед запуском тела конструктора. (Список инициализаторов элементов не совпадает со списком инициализаторов типа std::initializer_list .)

Предпочитать инициализаторы элементов перечисляют значения вместо назначения значений в тексте конструктора. Список инициализаторов элементов напрямую инициализирует элементы. В следующем примере показан список инициализаторов элементов, состоящий из всех identifier(argument) выражений после двоеточия:

Идентификатор должен ссылаться на член класса; он инициализирован со значением аргумента. Аргумент может быть одним из параметров конструктора, вызова функции или . std::initializer_list

const члены и члены ссылочного типа должны быть инициализированы в списке инициализаторов элементов.

Чтобы обеспечить полную инициализацию базовых классов перед запуском производного конструктора, вызовите все параметризованные конструкторы базового класса в списке инициализаторов.

Конструкторы по умолчанию

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

Конструкторы по умолчанию являются одной из специальных функций-членов. Если конструкторы в классе не объявляются, компилятор предоставляет неявный inline конструктор по умолчанию.

Если используется неявный конструктор по умолчанию, обязательно инициализировать элементы в определении класса, как показано в предыдущем примере. Без этих инициализаторов члены будут неинициализированы, а вызов Volume() создаст значение мусора. Как правило, рекомендуется инициализировать элементы таким образом, даже если не используется неявный конструктор по умолчанию.

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

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

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

Это утверждение является примером проблемы "Большинство vexing Parse". Можно интерпретировать myclass md(); как объявление функции или как вызов конструктора по умолчанию. Поскольку средства синтаксического анализа C++ предпочитают объявления по сравнению с другими вещами, выражение рассматривается как объявление функции. Дополнительные сведения см. в разделе "Большинство синтаксического анализа".

Если объявлены какие-либо конструкторы, отличные от по умолчанию, компилятор не предоставляет конструктор по умолчанию:

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

Однако для инициализации массива объектов Box можно использовать набор списков инициализаторов:

Дополнительные сведения см. в разделе "Инициализаторы".

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

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

Конструктор копирования может иметь одну из следующих сигнатур:

При определении конструктора копирования необходимо также определить оператор присваивания копирования (=). Дополнительные сведения см. в разделе "Назначение " и " Копирование конструкторов" и операторов присваивания копирования.

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

При попытке копирования объекта возникает ошибка C2280: попытка ссылаться на удаленную функцию.

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

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

Компилятор выбирает конструктор перемещения, когда объект инициализируется другим объектом того же типа, если другой объект будет уничтожен и больше не нуждается в его ресурсах. В следующем примере показано одно дело, когда конструктор перемещения выбирается с помощью разрешения перегрузки. В конструкторе, который вызывает get_Box() , возвращаемое значение является xvalue (значение eXpiring). Поэтому он не назначается какой-либо переменной и поэтому выходит за пределы области действия. Чтобы обеспечить мотивацию для этого примера, давайте предоставим Box большой вектор строк, представляющих его содержимое. Вместо копирования вектора и его строк конструктор перемещения "крадет" его из значения "box", чтобы вектор теперь принадлежит новому объекту. Вызов std::move необходим, так как оба vector класса string реализуют собственные конструкторы перемещения.

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

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

Дополнительные сведения о написании конструктора нетривиального перемещения см. в разделе "Конструкторы перемещения" и "Операторы присваивания перемещения" (C++).

Явно заданные по умолчанию и удаленные конструкторы

Конструкторы копирования по умолчанию , конструкторы по умолчанию, конструкторы перемещения, операторы присваивания копирования, операторы присваивания перемещения и деструкторы. Вы можете явно удалить все специальные функции-члены.

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

Конструктор может быть объявлен как constexpr , если

  • он либо объявлен как стандартный, либо удовлетворяет всем условиям для функций constexpr в целом;
  • класс не имеет виртуальных базовых классов;
  • каждый из параметров является литеральным типом;
  • тело не является блоком try-block функции;
  • инициализированы все нестатические члены данных и подобъекты базового класса;
  • Значение , если класс является (a) объединением, имеющим члены варианта, или (б) имеет анонимные объединения, инициализируется только один из членов профсоюза;
  • каждый нестатический член данных типа класса, а все подобъекты базового класса имеют конструктор constexpr.

Конструкторы списков инициализаторов

Затем создайте объекты Box следующим образом:

Явные конструкторы

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

Можно инициализировать Box следующим образом:

Или передать целое значение функции, принимающей объект Box:

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

Когда конструктор является явным, эта строка вызывает ошибку компилятора: ShippingOrder so(42, 10.8); . Дополнительные сведения см. в разделе о преобразованиях определяемых пользователем типов.

Порядок строительства

Конструктор выполняет свою работу в следующем порядке.

Вызывает конструкторы базовых классов и членов в порядке объявления.

Если класс является производным от виртуальных базовых классов, конструктор инициализирует указатели виртуальных базовых классов объекта.

Если класс имеет или наследует виртуальные функции, конструктор инициализирует указатели виртуальных функций объекта. Указатели виртуальных функций указывают на таблицу виртуальных функций класса, чтобы обеспечить правильную привязку вызовов виртуальных функций к коду.

Выполняет весь код в теле функции.

В следующем примере показан порядок, в котором конструкторы базовых классов и членов вызываются в конструкторе для производного класса. Сначала вызывается базовый конструктор. Затем члены базового класса инициализируются в том порядке, в котором они отображаются в объявлении класса. Наконец, вызывается производный конструктор.

Выходные данные будут выглядеть следующим образом.

Конструктор производного класса всегда вызывает конструктор базового класса, чтобы перед выполнением любых дополнительных операций иметь в своем распоряжении полностью созданные базовые классы. Конструкторы базового класса вызываются в порядке наследования, например, если ClassA является производным от , производным от ClassC ClassB которого является конструктор, ClassC сначала вызывается конструктор, а затем ClassB конструктор, а затем ClassA конструктор.

Если базовый класс не имеет конструктора по умолчанию, необходимо указать параметры конструктора базового класса в конструкторе производного класса:

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

Отменяется код в теле функции конструктора.

Объекты базовых классов и объекты-члены удаляются в порядке, обратном объявлению.

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

Производные конструкторы и расширенная инициализация агрегатов

Если конструктор базового класса не является открытым, но доступен для производного класса, нельзя использовать пустые фигурные скобки для инициализации объекта производного типа в /std:c++17 режиме, а затем в Visual Studio 2017 и более поздних версий.

В следующем примере показана соответствующая реакция на событие в C++14:

В C++17 Derived теперь считается агрегатным типом. Это означает, что инициализация Base через закрытый конструктор по умолчанию происходит непосредственно как часть расширенного правила агрегатной инициализации. Ранее частный Base конструктор был вызван через Derived конструктор, и он был успешно выполнен из-за friend объявления.

В следующем примере показано поведение C++17 в Visual Studio 2017 и более поздних версий в /std:c++17 режиме:

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

Если класс является производным от нескольких базовых классов, конструкторы базового класса вызываются в порядке, в котором они перечислены в объявлении производного класса:

Должны выводиться следующие выходные данные:

Делегирующие конструкторы

Делегирующий конструктор вызывает другой конструктор в том же классе для выполнения некоторых действий по инициализации. Эта функция полезна, если у вас есть несколько конструкторов, которые все должны выполнять аналогичную работу. Основную логику можно написать в одном конструкторе и вызвать из других. В следующем тривиальном примере Box(int) делегирует свою работу Box(int,int,int):

Объект, созданный конструкторами, полностью инициализируется сразу после выполнения любого конструктора. Дополнительные сведения см. в разделе "Делегирование конструкторов".

Наследование конструкторов (C++11)

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

Visual Studio 2017 и более поздних версий: оператор using в /std:c++17 режиме и более поздних версиях преобразует все конструкторы из базового класса, за исключением тех, которые имеют идентичную сигнатуру конструкторам в производном классе. Как правило, рекомендуется использовать наследуемые конструкторы, когда производный класс не объявляет новые члены данных или конструкторы.

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

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

Конструкторы и составные классы

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

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

Конструктор перемещения должен по сути переназначать указатели на внутреннее содержимое. Чтобы переназначить эти члены нужно видеть определение класса Matrix .

2 ответа 2

Конструктор может выглядеть следующим образом

У вас может возникать проблема из-за неправильно определенного деструктора. Как я понимаю, в деструкторе, прежде чем удалять каждый из элементов массива, вы должны проверить, не равен ли указатель m_matrix nullptr . Например

В вашем случае вы сначала присваиваете указателю (подозреваю, что m_matrix это указатель) на матрицу текущего объекта указатель из копируемого, то есть далее в цикле в строке m_matrix[i] = matrix.m_matrix[i]; вы присваиваете значение само себе, а в следующей строке обнуляете его, причем в обоих объектах.

Кроме того, во многих случаях, где копирующий конструктор должен выполнять глубокое копирование, стандартный перемещающий конструктор является пригодным, т.к. применяет функцию move к полям переносимого класса, поэтому, возможно и в вашей ситуации достаточно объявить его как Matrix::Matrix(Matrix &&matrix) =default;

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

@AnT, перемещающий конструктор по умолчанию применяет к полям объекта move , а не копирует. При применении move объект-источник перестает быть валидным. Про конструкторы по умолчанию.

Во-первых, "перестает быть валидным" - это концепция уровня пользователя. Это задача автора класса - обсепечить поддержание и распознание "невалидного" состояния после move. На уровне ядра языка же нет понятия какого-то детерминистского состояния "невалидного объекта". Если вы сделаете move из одного голого указателя в другой, исходный указатель никак не изменится и никаким магическим образом не станет "невалидным". Он просто скопируется и все.

Во-вторых, судя по всему, в данном случае полями объекта являются поля фундаментальных типов: указатель и два целых числа. Для фундаментальных типов операция move эквивалентна операции copy: они просто копируются и все. Они сам по себе никак не "перестают быть валидными". В данном случае defaulted move constructor будет просто прямым эквивалентом shallow copy constructor. Со всем вытекающими.

Вот если бы m_matrix был стандартным smart pointer, то тогда надежда на работоспособность defaulted move constructor была бы. Но это, очевидно, не так.

Зачем нужен конструктор перемещения, если есть оператор перемещения( operator=(T&&) )?

Это вопрос? Когда в качестве аргумента передается const Type& (еще возможна ситуация с Type x = y , так называемая, copy-initialization). Здесь можно прочитать подробнее

3 ответа 3

Область использования

Конструктор и оператор перемещения используются компилятором в разных ситуациях:

  • конструктор перемещения применяется в местах, где объявление совпадает с определением (инициализацией) rvalue-ссылкой на экземпляр этого же класса, либо посредством direct initialization в конструкторе класса/структуры (если же определение произойдет с помощью lvalue-ссылки, то вызовется конструктор копирования);
  • оператор перемещения применяется в местах, где экземпляр класса уже был ранее определен и к нему применяется operator = , который в качестве аргумента приминает rvalue-ссылку на экземпляр этого же класса (если же оператор принимает lvalue-ссылку , то вызовется оператор присваивания).

Про rvalue-ссылки можете почитать здесь, здесь и здесь.

Контрольный пример (для разъяснения отличия в работе данных конструкций)

Дополнение

В C++11 каждый класс, помимо конструктора по умолчанию, имеет следующие 5 дефолтных операций:

  1. конструктор копирования (copy constructor);
  2. оператор присваивания (copy assignment);
  3. конструктор перемещения (move constructor);
  4. оператор перемещения (move assignment);
  5. деструктор (destructor).

При определении одной из этих 5-ти операций рекомендуется явно указать (либо определить, либо объявить с помощью default или delete ) все остальные, т.к. все эти 5 операций тесно связаны. Это будет способствовать лучшему пониманию семантики класса при чтении кода.
Если явно определена одна из упомянутых 5-ти операций (в том числе с использованием default или delete ), то:

  • недостающие операции копирования будут определены автоматически с поведением по умолчанию;
  • недостающие операции перемещения определены не будут.

Это следует учитывать при написании классов.

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

Конструктор в общем случае работает на "сыром" (несконструированном, непроинициализорованном) блоке памяти. В момент начала работы конструктора объекта как такового еще не существует и он не имеет никакого предсказуемого состояния. Соответственно работа конструктора сводится к созданию/инициализации нового объекта в предоставленном блоке "сырой" памяти. Конструктор копирования, например, копирует это состояние из некоего объекта-образца, конструктор перемещения - перемещает, конструктор преобразования - преобразует и т.д. Конструктор перемещения никоим образом не выделяется из этого ряда.

Оператор присваивания же всегда имеет дело с уже проинициализированным/сконструированным объектом, находящимся в некоем предсказуемом "валидном" состоянии. Работа оператора присваивания сводится к освобождению исходного состояния объекта (освобождению ресурсов, например), за которым следует копирование (или перемещение, или преобразование и т.п.) нового состояния из некоего объекта-источника.

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

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

В принципе, стандартная реализация оператора присваивания могла бы делать следующее: для объекта в левой части оператора присваивания вызвать сначала деструктор, а потом конструктор копирования, передав ему ссылку на объект в правой части. Наверное, это было бы лучше, чем просто копировать все поля из правого объекта в левый, но всё равно это было бы не совсем правильно. Дело в том, что при копировании объекта требуется выделить новый ресурс, но потенциально это может закончиться неудачей. Если это произойдёт, то после неудачной попытки присваивания объект в левой части останется в некорректном состоянии: старый ресурс уже освобождён (вызван деструктор), а новый захватить не получилось. Поэтому при перегрузке оператора присваивания нужно всегда сначала захватывать новый ресурс, и только если эта операция прошла успешно, освобождать старый, заменяя его новым (по этой причине оператор присваивания в принятом ответе написан не совсем правильно). В случае перемещения, в принципе, такой проблемы нет: копируется только ссылка на ресурс, и старый ресурс, которым владел объект в левой части оператора перемещения, может быть освобождён безболезненно, как до копирования ссылки, так и после. Скорее всего, аналогичное копированию разделение на конструктор и оператор в случае перемещения сделано для случая, когда неудачей закончилось освобождение ресурса в левой части оператора перемещения. В этой ситуации необходимо, чтобы объект в правой части продолжал владеть ресурсом. Таким образом сохраняется атомарность копирования и перемещения: либо нам удалось полностью создать копию объекта или полностью переместить ресурс из одного объекта в другой, либо копирование / перемещение прошло неудачно, и ни один из объектов не был изменён.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками c++ c++11 или задайте свой вопрос.

Связанные

Похожие

Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.

Site design / logo © 2022 Stack Exchange Inc; user contributions licensed under cc by-sa. rev 2022.6.24.42450

Нажимая «Принять все файлы cookie», вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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