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

Обновлено: 27.04.2024

Как известно, в языке С для динамического выделения и освобождения памяти используются фун­кции malloc() и free(). Вместе с тем С++ содержит два оператора, выполняющих выделение и освобождение памяти более эффективно и более просто. Этими операторами являются new и delete. Их общая форма имеет вид:

переменная_указатель = new тип_переменной;

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

Если операция выделения памяти не может быть выполнена, то оператор new генерирует ис­ключение типа xalloc. Если программа не перехватит это исключение, тогда она будет снята с выполнения. Хотя для коротких программ такое поведение по умолчанию является удовлетвори­тельным, для реальных прикладных программ обычно требуется перехватить исключение и обра­ботать его соответствующим образом. Для того чтобы отследить это исключение, необходимо вклю­чить заголовочный файл except.h.

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

Есть ряд преимуществ использования new перед использованием malloc(). Во-первых, оператор new автоматически вычисляет размер необходимой памяти. Нет необходимости в использовании оператора sizeof(). Более важно то, что он предотвращает случайное выделение неправильного количества памяти. Во-вторых, оператор new автоматически возвращает указатель требуемого типа, так что нет необходимости в использовании оператора преобразования типа. В-третьих, как ско­ро будет описано, имеется возможность инициализации объекта при использовании оператора new. И наконец, имеется возможность перегрузить оператор new и оператор delete глобально или по отношению к тому классу, который создается.

Ниже приведен простой пример использования операторов new и delete. Следует обратить вни­мание на использование блока try/catch для отслеживания ошибок выделения памяти.

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

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

С помощью new можно размещать массивы. Общая форма для одномерного массива имеет вид:

переменная_указатель = new тип_переменной [размер];

Здесь размер определяет число элементов в массиве. Необходимо запомнить важное ограничение при размещении массива: его нельзя инициализировать.

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

Здесь скобки [] информируют оператор delete, что необходимо освободить память, выделенную для массива.

В следующей программе выделяется память для массива из 10 элементов типа float. Элементам массива присваиваются значения от 100 до 109, а затем содержимое массива выводится на экран:

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

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

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

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

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

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

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

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

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

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

4. Пример

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

Всем привет!
Вобщем решил вчера потренироваться в недавно-пройденном материале по выделению памяти new для указателей на данные в конструкторах класса и delete в деструкторе. И что-то в совсем застрял. А именно проблема с управлением памятью с помощью кострукторов копирования и присваивания. Накалякал следующее:
work.h

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

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

Кстати, зачем выделять память под string, не проще сделать его обычной переменной класса?

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

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

.
И, по задумке автора, должен считать общее количество существующих объектов типа Work. Это же одно число, верно?

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

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

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

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

Да на это пофигу, он там для наглядности чисто. Не обращайте на эти мелочи внимание, мне важно сам принцип управлением памяти понять.

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

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

Всем привет!
Вобщем решил вчера потренироваться в недавно-пройденном материале по выделению памяти new для указателей на данные в конструкторах класса и delete в деструкторе. И что-то в совсем застрял. А именно проблема с управлением памятью с помощью кострукторов копирования и присваивания. Накалякал следующее:
work.h

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

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

Кстати, зачем выделять память под string, не проще сделать его обычной переменной класса?

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

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

.
И, по задумке автора, должен считать общее количество существующих объектов типа Work. Это же одно число, верно?

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

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

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

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

Да на это пофигу, он там для наглядности чисто. Не обращайте на эти мелочи внимание, мне важно сам принцип управлением памяти понять.

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

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

По умолчанию при инициализации одного объекта другим С++ выполняет побитовое копирова­ние. Это означает, что точная копия инициализирующего объекта создается в целевом объекте. Хотя в большинстве случаев такой способ инициализации объекта является вполне приемлемым, имеются случаи, когда побитовое копирование не может использоваться. Например, такая ситу­ация имеет место, когда объект выделяет память при своем создании. Рассмотрим в качестве при­мера два объекта А и В класса ClassType, выделяющего память при создании объекта. Положим, что объект А уже существует. Это означает, что объект A уже выделил память. Далее предполо­жим, что А использовался для инициализации объекта B, как показано ниже:

Если в данном случае используется побитовое копирование, то В станет точной копией А. Это означает, что В будет использовать тот же самый участок выделенной памяти, что и A, вместо того, чтобы выделить свой собственный. Ясно, что такая ситуация нежелательна. Например, если класс ClassType включает в себя деструктор, освобождающий память, то тогда одна и та же па­мять будет освобождаться дважды при уничтожении объектов A и B!

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

Для решения подобных проблем язык С++ позволяет создать конструктор копирования, кото­рый используется компилятором, когда один объект инициализирует другой. При наличии кон­структора копирования побитовое копирование не выполняется. Общая форма конструктора ко­пирования имеет вид:

имя_класса (const имя_класса &о) // тело конструктора
>

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

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

myclass х = у; // инициализация
func (х); // передача параметра
у = func (); // получение временного объекта

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

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

Конструктор копирования выделяется только для инициализации. Например, следующие инст­рукции не содержат вызова конструктора копирования:

array а (10);
.
array b (10);
b = а; //не вызывает конструктор копирования

В данном случае b=а выполняет операцию присваивания. Если оператор = не перегружен, то будет сделана побитовая копия. Поэтому в определенных случаях требуется перегрузить опера­тор = в дополнение к созданию конструктора копирования.

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