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

Обновлено: 01.05.2024

Специальные функции-члены - это такие функции члены, которые неявно определены как члены класса при некоторых условиях. Всего их шесть:

Рассмотрим каждый из них.

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

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

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

Компилятор предполагает, что Example имеет конструктор по умолчанию. Таким образом, объекты этого класса могут быть созданы простым объявлением их без каких-либо аргументов:

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

Здесь мы объявили конструктор с параметром типа int. Поэтому следующее объявление объекта будет корректным:

В отличие от этого:

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

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

Здесь Example3 имеет конструктор по умолчанию (т.е. конструктор без параметров), определенный как пустой блок:

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

И когда любой конструктор явно определен в классе, то конструктор по умолчанию не предоставляется автоматически.

Деструктор

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

Но сейчас давайте представим, что класс в последнем примере выделяет динамическую память для хранения строки, которая является его членом. В этом случае будет полезно иметь функцию, вызываемую автоматически в конце жизни объекта, для освобождения этой памяти. Чтобы это сделать, мы используем деструктор. Деструктор - это метод, очень похожий на конструктор по умолчанию: он не принимает аргументов и ничего не возвращает, даже void. В качестве имени он также использует имя класса, перед которым ставится знак тильда (~):

При создании Example4 выделяет память для string. Эта память позже освобождается деструктором.

Деструктор для объекта вызывается в конце его жизни. В случае foo и bar это происходит в конце функции main().

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

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

Конструктор копирования - это конструктор, первый параметр которого имеет тип ссылки на собственный класс (возможно, объявленный как const) и который может быть вызван с одним аргументом этого типа. Например, для класса MyClass конструктор копирования может иметь следующую сигнатуру:

Если класс не имеет других определенных конструкторов копирования или перемещения (или присваивания), неявно предоставляется конструктор копирования. Этот конструктор просто производит копию своих членов. Например, для такого класса, как этот:

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

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

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

Присваивание копированием

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

Отметим, что baz инициализируется в конструкторе с использованием знака присваивания, но это не операция присваивания! (хотя это может так выглядеть): это просто другой синтаксис для вызова конструктора с одним аргументом.

Присваивание foo это операция присваивания. Здесь не объявляется объектов, но производится операция над существующим объектом foo.

Оператор присваивания копированием это перегрузка оператора operator=, который принимает значение или ссылку на объект такого же класса как параметр. Возвращаемое значение это обычно ссылка на *this (хотя это и не требуется). Например, для класса MyClass присваивание копированием может иметь следующую сигнатуру:

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

Однако неявная версия производит поверхностное копирование, которое приемлемо для многих классов, но не для классов с указателями на хранимые ими объекты, как в случае с Example5. В этом случае не только класс подвергается риску удаления объекта, на который он указывает, дважды, но и создает утечки памяти, не удаляя объекты, на которые он указывал до присваивания. Эти проблемы могут быть решены использованием присваивания копированием, которое удаляет старый объект и производит глубокое копирование:

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

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

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

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

Использование значения временных объектов как эти для инициализации других объектов или присваивания им значения действительно не требует копии: объект никогда не будет использован где-либо еще и поэтому его значение может быть перемещено в целевой объект. Такие случаи инициируют конструктор перемещения или присваивание перемещением:

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

Значение, возвращаемое fn() и значение, создаваемое MyClass(), это безымянные временные объекты. В таких случаях нет необходимости создавать копию, потому что безымянные объекты существуют очень недолго и могут быть присвоены другим объектам в момент, когда это наиболее эффективно.

Конструктор перемещения и присваивание перемещением - это методы, которые принимают параметр типа ссылки rvalue на объект своего собственного класса:

Ссылка rvalue определяется передачей типа с двумя амперсандами (&&). Как параметр, ссылка rvalue соответствует аргументам временных объектов этого типа.

Концепция перемещения наиболее полезна для объектов, которые управляют используемой ими памятью, такие, как объекты, выделяющие память с помощью new и удаляющие ее с помощью delete. Для таких объектов копирование и перемещение это действительно разные операции:

Копирование из A в B означает, что новая память выделяется для B и затем все содержимое A копируется в эту новую память, выделенную для B.

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

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

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

Члены класса, определенные неявно

Шесть специальных методов, описанных выше, это члены, неявно объявленные в классе при определенных обстоятельствах:

Обратите внимание, что не все специальные методы неявно определяются в одних и тех же случаях. Это обычно делается для обратной совместимости со структурами языка Си и более ранними версиями языка C++. Фактически, многие из этих случаев являются устаревшими. К счастью, каждый класс может явно указать, какие из этих членов существует с определением по умолчанию, а какие удаляются с использованием ключевых слов default и delete соответственно. Их синтаксис:

Здесь Rectangle может быть создан либо с двумя аргументами типа int, либо конструктором по умолчанию (без аргументов).

Он, однако, не может быть скопирован из другого объекта типа Rectangle конструктором копирования, потому что эта функция удалена. Поэтому, применимо к объекта в последнем примере, следующее выражение будет некорректным:

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

Что будет эквивалентно:

Отметим, что ключевое слово default не определяет метод, эквивалентный конструктору по умолчанию (т.е. когда конструктор по умолчанию - это конструктор без параметров), но эквивалентен конструктору, который будет неявно определен, если не удален.

В основном, в т.ч. и для будущей совместимости, является хорошим тоном для классов, которые явно определяют один конструктор копирования/перемещения или один оператор присваивания копированием/перемещением, но не оба, определить также delete или default для другого специального метода, который в нем не определен.

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

1. Конструктор копирования

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

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

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

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

2. Перегруженная операция присваивания

Перегруженная операция присваивания используется при присваивании одного объекта другому существующему объекту. Здесь присутствует такая же проблема, что и в конструкторе копирования. К тому же, у объекта, которому присваивается значение, уже может быть выделена динамическая память. Перед присваиванием новых данных, выделенную ранее память необходимо очистить, чтобы не допустить её утечки (см. пример в конце). Также необходимо обработать случай самоприсваивания. В противном случае, данные в динамической памяти просто будут утеряны. Аналогично копированию, присваивание также можно запретить, поместив операцию в приватной области класса.

3. Деструктор

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

4. Пример

Стоить отметить, что во всех трёх функциях память должна выделяться и удаляться одинаковым образом. Т. е. нельзя в одном случае использовать delete, а в другом delete[].

Конструктор - блок инструкций, вызываемый при создании объекта класса.

Деструктор - блок инструкций, вызываемый при уничтожении объекта класса.

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

Назначение конструктора: присвоение каких-то значений полям, выделение памяти, открытие файлов и установление сетевых соединений.

Назначение деструктора: соответственно, наоборот очистка памяти и сохранение файлов.

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

Оформление конструктора (внутри класса):

Оформление деструктора (внутри класса):

Пример вызова конструктора:

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

Для конструктора, конструкторов копирования и перемещения, а также деструктора справедливо следующее: если программист не написал их сам, то компилятор допишет их за него. Такие конструкторы(деструкторы) называются неявными. Также важно понимать, что конструктор, декструтор, к.копирования и к.перемещения должны иметь доступ модификатор доступа public.

Это конструктор вида (для случая внутри класса):

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

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

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

В данном примере для a2 и a3 будет вызван конструктор копирования, который скопирует каждое поле a1.

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

Если мы захотим сломать конструктор копирования, указав явно только один его вариант, который принимает не только ссылку, но и другие аргументы, то программа сама додумает за нас самый просто конструктор копирования с единственным аргументом (ссылкой). И пример, приведённый выше, всё ещё будет работать. А вот если мы сами напишем конструктор копирования с одним аргументом (ссылкой) и добавим к нему слово explicit, то пример работать перестанет, так как из-за третьей строчки программа не скомпилируется.

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

Если мы переопределили конструктор копирования, чтобы он, например, менял местами поля и дописывал всякие нехорошие слова к полям-строкам, а затем запустили пример, приведённый выше, то эти действия произведутся только для a1, а объект a2 будет идентичен объекту b.

Это конструктор вида (для случая внутри класса):

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

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

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

date today = date(6,4,2014); // полная форма
date xmas(25,12,0); // сокращенная форма
// date my_burthday; // недопустимо, опущена инициализация

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

class date <
int month, day, year;
public :
date( int , int , int ); // день месяц год
date( char *); // дата в строковом представлении
date(); // дата по умолчанию: сегодня
>;

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

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

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

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

class date
int month, day, year;
public :
date( int , int , int );
date( char *);
date(); // конструктор по умолчанию
>;

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

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

  • формальный параметр – объект, передаваемый по значению, создается в стеке в момент вызова функции и инициализируется копией фактического параметра;
  • результат функции – объект, передаваемый по значению, в момент выполнения оператора return копируется во временный объект, сохраняющий результат функции.

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

  • date2 в приведенном определении;
  • для создаваемого в стеке формального параметра;
  • для временного объекта, сохраняющего значение, возвращаемое функцией.

Вместо этого в них копируется содержимое объекта-источника:

  • date1 в приведенном примере;
  • фактического параметра;
  • объекта-результата в операторе return .

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

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

Деструкторы

Определяемый пользователем класс имеет конструктор, который обеспечивает надлежащую инициализацию. Для многих типов также требуется обратное действие. Деструктор обеспечивает соответствующую очистку объектов указанного типа. Имя деструктора представляет собой имя класса с предшествующим ему знаком «тильда» ~ . Так, для класса X деструктор будет иметь имя ~X() . Многие классы используют динамическую память, которая выделяется конструктором, а освобождается деструктором.

class date
int day, year;
char *month;
public :
date( int d, char * m, int y)
day = d;
month = new char [strlen(m)+1];
strcpy_s(month, strlen(m)+1,m);
year = y;
>
~date() < delete [] month; >// деструктор
>;

Поля, имеющие тип класса

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

Конструктор нового класса имеет пустое тело и список вызываемых конструкторов класса vect , перечисленных после двоеточия (:) через запятую (,). Они выполняются с целым аргументом i , создавая 3 объекта класса vect: a, b, c .

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

Здравствуйте, у меня такой вопрос. В коде я не нашел сам деструктор. Он создается и запускается сам после выхода из блока main ?

Этот раздел является переводом туториала C++ Language

[ПРИМЕЧАНИЕ: этот раздел требует хорошего понимания динамически выделяемой памяти]

Специальные функции-члены - это такие функции члены, которые неявно определены как члены класса при некоторых условиях. Всего их шесть:

Рассмотрим каждый из них.

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

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

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

Компилятор предполагает, что Example имеет конструктор по умолчанию. Таким образом, объекты этого класса могут быть созданы простым объявлением их без каких-либо аргументов:

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

Здесь мы объявили конструктор с параметром типа int. Поэтому следующее объявление объекта будет корректным:

В отличие от этого:

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

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

Здесь Example3 имеет конструктор по умолчанию (т.е. конструктор без параметров), определенный как пустой блок:

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

И когда любой конструктор явно определен в классе, то конструктор по умолчанию не предоставляется автоматически.

Деструктор

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

Но сейчас давайте представим, что класс в последнем примере выделяет динамическую память для хранения строки, которая является его членом. В этом случае будет полезно иметь функцию, вызываемую автоматически в конце жизни объекта, для освобождения этой памяти. Чтобы это сделать, мы используем деструктор. Деструктор - это метод, очень похожий на конструктор по умолчанию: он не принимает аргументов и ничего не возвращает, даже void. В качестве имени он также использует имя класса, перед которым ставится знак тильда (~):

При создании Example4 выделяет память для string. Эта память позже освобождается деструктором.

Деструктор для объекта вызывается в конце его жизни. В случае foo и bar это происходит в конце функции main().

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

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

Конструктор копирования - это конструктор, первый параметр которого имеет тип ссылки на собственный класс (возможно, объявленный как const) и который может быть вызван с одним аргументом этого типа. Например, для класса MyClass конструктор копирования может иметь следующую сигнатуру:

Если класс не имеет других определенных конструкторов копирования или перемещения (или присваивания), неявно предоставляется конструктор копирования. Этот конструктор просто производит копию своих членов. Например, для такого класса, как этот:

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

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

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

Присваивание копированием

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

Отметим, что baz инициализируется в конструкторе с использованием знака присваивания, но это не операция присваивания! (хотя это может так выглядеть): это просто другой синтаксис для вызова конструктора с одним аргументом.

Присваивание foo это операция присваивания. Здесь не объявляется объектов, но производится операция над существующим объектом foo.

Оператор присваивания копированием это перегрузка оператора operator=, который принимает значение или ссылку на объект такого же класса как параметр. Возвращаемое значение это обычно ссылка на *this (хотя это и не требуется). Например, для класса MyClass присваивание копированием может иметь следующую сигнатуру:

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

Однако неявная версия производит поверхностное копирование, которое приемлемо для многих классов, но не для классов с указателями на хранимые ими объекты, как в случае с Example5. В этом случае не только класс подвергается риску удаления объекта, на который он указывает, дважды, но и создает утечки памяти, не удаляя объекты, на которые он указывал до присваивания. Эти проблемы могут быть решены использованием присваивания копированием, которое удаляет старый объект и производит глубокое копирование:

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

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

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

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

Использование значения временных объектов как эти для инициализации других объектов или присваивания им значения действительно не требует копии: объект никогда не будет использован где-либо еще и поэтому его значение может быть перемещено в целевой объект. Такие случаи инициируют конструктор перемещения или присваивание перемещением:

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

Значение, возвращаемое fn() и значение, создаваемое MyClass(), это безымянные временные объекты. В таких случаях нет необходимости создавать копию, потому что безымянные объекты существуют очень недолго и могут быть присвоены другим объектам в момент, когда это наиболее эффективно.

Конструктор перемещения и присваивание перемещением - это методы, которые принимают параметр типа ссылки rvalue на объект своего собственного класса:

Ссылка rvalue определяется передачей типа с двумя амперсандами (&&). Как параметр, ссылка rvalue соответствует аргументам временных объектов этого типа.

Концепция перемещения наиболее полезна для объектов, которые управляют используемой ими памятью, такие, как объекты, выделяющие память с помощью new и удаляющие ее с помощью delete. Для таких объектов копирование и перемещение это действительно разные операции:

Копирование из A в B означает, что новая память выделяется для B и затем все содержимое A копируется в эту новую память, выделенную для B.

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

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

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

Члены класса, определенные неявно

Шесть специальных методов, описанных выше, это члены, неявно объявленные в классе при определенных обстоятельствах:

Обратите внимание, что не все специальные методы неявно определяются в одних и тех же случаях. Это обычно делается для обратной совместимости со структурами языка Си и более ранними версиями языка C++. Фактически, многие из этих случаев являются устаревшими. К счастью, каждый класс может явно указать, какие из этих членов существует с определением по умолчанию, а какие удаляются с использованием ключевых слов default и delete соответственно. Их синтаксис:

Здесь Rectangle может быть создан либо с двумя аргументами типа int, либо конструктором по умолчанию (без аргументов).

Он, однако, не может быть скопирован из другого объекта типа Rectangle конструктором копирования, потому что эта функция удалена. Поэтому, применимо к объекта в последнем примере, следующее выражение будет некорректным:

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

Что будет эквивалентно:

Отметим, что ключевое слово default не определяет метод, эквивалентный конструктору по умолчанию (т.е. когда конструктор по умолчанию - это конструктор без параметров), но эквивалентен конструктору, который будет неявно определен, если не удален.

В основном, в т.ч. и для будущей совместимости, является хорошим тоном для классов, которые явно определяют один конструктор копирования/перемещения или один оператор присваивания копированием/перемещением, но не оба, определить также delete или default для другого специального метода, который в нем не определен.

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