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

Обновлено: 03.05.2024

Начиная с C++11, в языке поддерживаются два типа присваивания: назначение копирования и перемещение. В этой статье "присваивание" означает "присваивание копированием", если явно не указано другое. Сведения о назначении перемещения см. в разделе "Конструкторы перемещения" и "Операторы присваивания перемещения" (C++).

Как при операции назначения, так и при операции инициализации выполняется копирование объектов.

Назначение: когда одному объекту присваивается значение другого объекта, первый объект копируется во второй объект. Таким образом, этот код копирует значение b в a :

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

Можно определить семантику копии объектов типа класса. Рассмотрим для примера такой код:

Приведенный выше код может означать копирование содержимого ФАЙЛА 1. DAT в FILE2. DAT или это может означать "игнорировать FILE2". DAT и сделайте b второй дескриптор в FILE1.DAT". Необходимо присоединить соответствующую семантику копирования к каждому классу следующим образом:

Используйте оператор operator= присваивания, который возвращает ссылку на тип класса и принимает один параметр, передаваемый по const ссылке, например ClassName& operator=(const ClassName& x); .

Используйте конструктор копирования.

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

Конструктор копирования принимает аргумент типа ClassName& , где ClassName — имя класса. Пример:

По возможности сделайте тип аргумента const ClassName& конструктора копирования. Это предотвращает случайное изменение скопированного объекта конструктором копирования. Он также позволяет копировать из const объектов.

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

Конструкторы копирования, созданные компилятором, такие как пользовательские конструкторы копирования, имеют один аргумент типа "ссылка на имя класса". Исключением является то, что все базовые классы и классы-члены имеют конструкторы копирования, объявленные как принимающие один аргумент const типа class-name&. В таком случае аргумент конструктора копирования, созданного компилятором, также const является .

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

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

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

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

Дополнительные сведения о перегруженных операторах присваивания см. в разделе "Назначение".

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

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

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

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

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

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

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

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

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

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

4. Пример

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

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

Идея программы заключается в следующем. У меня есть класс Labtest с двумя закрытыми полями. ntest целое число, которое содержит количество тестов и значений, которое является динамическим массивом с ntest элементами.

Я добавил метод add, который расширяет динамический массив на единицу и добавляет аргумент в последнюю позицию.

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

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

Код, который я написал до сих пор,

Я получаю ошибку из кучи границ в Visual Studio. Даже если я прокомментирую вызов функции max.

Любая помощь будет оценена!

Решение

new int(10) делает int переменная со значением 10. new int[10] делает массив из 10 int s. Из контекста ясно, что вы намеревались использовать вторую форму. Использование первой формы приведет к неопределенному поведению при вызове delete[] values; (поскольку values не будет массив) или когда вы получили доступ к любым элементам, кроме первого.

Другие решения

Когда вы выделяете массивы, вы должны сделать int *newvalues = new int[ntests+1]; используя скобки вместо []

Шаблонный класс sdt :: vector просто обеспечивает всю необходимую вам функциональность — без привлечения вас к деталям управления памятью.
Но если вы хотите попрактиковаться в управлении памятью, я должен напомнить вам о 3 примечаниях:

Синтаксис массива новый оператор new type_spec[n] Не новый type_spec(n) , Синтаксис, который вы использовали, просто выделяет один объект и инициализирует его с помощью n. Поэтому удаление его с помощью оператора удаления массива приводит к UB.

Безопаснее обнулить указатель после удаления.

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

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

Оператор перемещения / копирования или оба, с точки зрения обмена.

Деструктор с точки зрения обмена.

В противном случае — рано или поздно — нарушение 1 или более правил обработки ресурсов происходит из-за забвения неявной генерации временного объекта или другой семантики. И ненормальное поведение будет иметь место.

Я работал над проектом, в котором я должен воссоздать основы функции, используя динамический массив. Программа должна иметь конструктор по умолчанию, который создает массив размером 2, а размер «вектора» равен 0, пока в класс вектора не будет добавлено больше значений с именем DynArray. Когда есть попытка вставить .push_back в вектор, когда массив заполнен, моя программа должна создать новый экземпляр массива с удвоенной емкостью и удалить старый массив. Это то, что я имею до сих пор, и я изо всех сил пытаюсь выяснить, как конструктор копирования должен быть настроен так, чтобы он работал должным образом.

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

Решение

Ваш конструктор копирования может получить доступ к закрытым членам объекта, который он копирует, поэтому он должен быть очень похож на ваш метод изменения размера. Есть ярлыки (memcpy, std :: copy), но вам лучше научиться использовать цикл.

Итак, список небольших проблем, которые у вас есть:

  • в конструкторе копирования установите размер, равный размеру исходного объекта. (хотя, вы могли бы потенциально уменьшить его до arraySize).
  • скопировать все элементы с помощью функции циклического или массового копирования (memcpy)
  • Вам не хватает> в вашем методе push_back
  • pushCounter всегда точно такой же, как arraySize. Вам не нужны оба
  • Я должен быть локальной переменной, а не членом класса.
  • Члены класса atNum и newNum не используются.

Другие решения

Несколько очевидных ошибок:

Это, вероятно, опечатка. Нет такого типа как DynArry ,

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

Это сохраняет адрес динамического массива в локальной автоматической переменной. Как только конструктор заканчивается, указатель — будучи автоматическим — уничтожается, а массив пропускается. Вы, вероятно, должны внести изменения в this->arrayVector вместо.

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

Кроме того, конструктор копирования оставляет многие элементы неинициализированными (например, arraySize ).

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

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

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

Ваш конструктор копирования должен сделать копию объекта, переданного ему.

Вместо этого он делает это;

Игнорирование вывода в cout две очевидные проблемы заключаются в том, что arrayVector динамически распределяется с двумя элементами, независимо от того, сколько элементов в origClass.arrayVector ,

Заявление int *arrayVector = *(origClass.arrayVector) и вообще не копирует массив.

  • Прежде всего он создает указатель с именем arrayVector (который не связан с членом DynArray несмотря на то же имя).
  • Затем он копирует первый элемент из origClass.arrayVector (а
    не замужем int ) к первый элемент из arrayVector , Так что, это
    функционально эквивалентно arrayVector[0] =
    origClass.arrayVector[0] , Так работают указатели.
  • Наконец, когда конструктор возвращается, arrayVector Объявленный перестает существовать, потому что если является локальным для тела конструктора. Следствием этого является утечка динамически выделяемой памяти (без переменных или членов вашей DynArray класс, обратитесь к нему вообще, поэтому в стандартном C ++ нет способа найти эту память в последующем коде).

Наконец, ваш конструктор копирования не копирует других членов origClass ( arraySize , arrayCapacity , newNum , pushCounter , atNum , i )
на новый строящийся объект.

Вместо этого вам (как правило) нужно скопировать ВСЕХ участников, а не только одного из них. И, если один из копируемых элементов является динамически размещаемым массивом — как в вашем случае — конструктор должен сначала создать новый массив, а затем скопировать все элементы в него — по одному за раз в цикле некоторой формы.

Например, вы могли бы сделать

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

Что это не сделало, это скопировать данные из origClass.arrayVector , Как я уже говорил выше, это не может быть сделано с помощью *arrayVector = *(origClass.arrayVector) , Вместо этого конструктор должен сделать это

Этот шаг необходим для обеспечения ВСЕХ элементов origClass.arrayVector копируются. Существуют различные варианты того, КАК это делается (например, с использованием синтаксиса указателя, а не синтаксиса массива), но вышеупомянутого будет достаточно.

Затем необходимо скопировать всех остальных членов из origClass ,

Как в сторону, мне кажется, ты дал DynArray количество членов ему не нужно. Некоторые из этих членов нужны только как временные функции в определенных функциях-членах (например, счетчики циклов). Это означает, что каждый случай DynArray имеет свою собственную копию этих переменных. В этом нет необходимости — им лучше быть локальными переменными, объявленными в каждой функции, где они необходимы.

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

Поэтому в конце я бы реализовал конструктор копирования как

Несмотря на мое предыдущее мнение, что у вас есть ненужные члены класса, список инициализаторов здесь все еще копирует ВСЕ ваши DynArray члены.

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

Также обратите внимание, что переменная i (счетчик цикла в теле конструктора) не имеет отношения к члену DynArray названный i ,

Я создаю класс, который будет отслеживать студентов. В этом классе я использую перегруженный = для копирования этих объектов ученика. Для отслеживания их классов я использую динамический массив. Массив копирует просто отлично; однако, при очистке переменных объекта ученика, любой объект, который был скопирован из него ранее, также имеет свой массив уничтожен. Вот код:

Главные преступники почти наверняка являются одним из этих двух

Выход для программы:

Что я могу сделать неправильно, чтобы вызвать это?

Решение

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

В этом классе также отсутствует конструктор копирования.

В операторе присваивания:

Это простой указатель. Для начала, это утечка памяти. Предыдущий указатель (если он есть) теряется, а выделенная ему память теряется.

Теперь у вас есть два экземпляра вашего класса с одинаковым classList указатель. Следовательно, когда resetClasses() метод вызывается в одном из этих классов, он будет удален, а поскольку другой экземпляр класса имеет тот же указатель, теперь он указывает на удаленную память.

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

В этом классе также отсутствует деструктор, что приведет к очередной утечке памяти.

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

Другие решения

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

Освобождение одного из этих полей приведет к освобождению другого.

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

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