Для чего нужен конструктор копирования
Конструкторы копий и операторы присваивания копий (C++)
Начиная с C++ 11, в языке поддерживаются два вида присваивания: копирование назначения и Перемещение. В этой статье «присваивание» означает «присваивание копированием», если явно не указано другое. Сведения о назначении Move см. в разделе конструкторы Move и операторы присваивания перемещения (C++).
Как при операции назначения, так и при операции инициализации выполняется копирование объектов.
Назначение: когда значение одного объекта присваивается другому объекту, первый объект копируется во второй объект. Поэтому
Инициализация: инициализация происходит при объявлении нового объекта, когда аргументы передаются в функции по значению или когда значения возвращаются из функций по значению.
Можно определить семантику копии объектов типа класса. Рассмотрим для примера такой код:
Приведенный выше код может означать «копировать содержимое файла FILE1. DAT в FILE2. DAT» или это может означать «Ignore FILE2. DAT и создайте b второй обработчик для file1. dat. » Необходимо прикрепить соответствующую семантику копирования к каждому классу, как показано ниже.
С помощью конструктора копии.
Если конструктор копии не объявлен, компилятор создает конструктор копии для каждого члена. Если оператор присваивания копированием не объявлен, компилятор создает оператор присваивания копированием для каждого члена. Объявление конструктора копии не подавляет созданный компилятором оператор присваивания копий, и наоборот. При реализации любого из этих способов рекомендуется также реализовать другой способ, чтобы значение кода было четким.
Конструкторы копии, создаваемые компилятором
Если виртуальные базовые классы инициализируются конструкторами копии, создаются компиляторами или определяются пользователем, они инициализируются только один раз, во время создания.
Дополнительные сведения о перегруженных операторах присваивания см. в разделе назначение.
Урок №141. Конструктор копирования
Обновл. 13 Сен 2021 |
Вспомним все типы инициализации, которые поддерживает язык C++: прямая инициализация, uniform-инициализация и копирующая инициализация.
Конструктор копирования
Рассмотрим примеры всех вышеприведенных инициализаций на практике, используя следующий класс Drob:
Мы можем выполнить прямую инициализацию:
В C++11 мы можем выполнить uniform-инициализацию:
И, наконец, мы можем выполнить копирующую инициализацию:
С прямой инициализацией и uniform-инициализацией создаваемый объект непосредственно инициализируется. Однако с копирующей инициализацией дела обстоят несколько сложнее. Мы рассмотрим это детально на следующем уроке. Но перед этим нам еще нужно кое в чём разобраться.
Рассмотрим следующую программу:
Результат выполнения программы:
Рассмотрим детально, как работает эта программа.
Конструктор копирования — это особый тип конструктора, который используется для создания нового объекта через копирование существующего объекта. И, как в случае с конструктором по умолчанию, если вы не предоставите конструктор копирования для своих классов самостоятельно, то язык C++ создаст public-конструктор копирования автоматически. Поскольку компилятор мало знает о вашем классе, то по умолчанию созданный конструктор копирования будет использовать почленную инициализацию. Почленная инициализация означает, что каждый член объекта-копии инициализируется непосредственно из члена объекта-оригинала. Т.е. в примере, приведенном выше, dCopy.m_numerator будет иметь значение sixSeven.m_numerator ( 6 ), а dCopy.m_denominator будет равен sixSeven.m_ denominator ( 7 ).
Так же, как мы можем явно определить конструктор по умолчанию, так же мы можем явно определить и конструктор копирования. Конструктор копирования выглядит следующим образом:
Необходимость конструктора копирования в С++
В языке С++, в отличие, к примеру, от С# присутствует понятие конструктора копирования. Этот конструктор вызывается при создании копии объекта некоторого класса, что инициализирует новый объект того же типа. Этот же конструктор вызывается и в менее очевидных случаях, когда происходит создание временной копии некоторого объекта.
Инициализация нового объекта другим объектом того же типа
К примеру, при инициализации некоторого объекта А типа MyClassобъектом В того же типа происходит создание побитовой копии объекта Bс последующей ее присваиванием объекту А.
При этом происходит так называемое «поверхностное» копирование. Этот вид копирования в отличие от «глубокого» предполагает копирование значений элементов объекта в соответствующие элементы другого объекта. Однако если одним из этих элементов окажется указатель на выделенную память, тогда согласно такому копированию будет скопировано лишь значение самого указателя в соответствующее поле другого объекта, содержащее аналогичный указатель. В таком случае мы окажемся перед ситуацией, когда два указателя из разных объектов того же типа будут указывать на одну и ту же область памяти. Такой подход создает явные сложности при необходимости удаления объектов. Удаление объекта В повлечет за собой и высвобождение памяти, на которую указывает указатель. Однако на ту же память продолжает указывать указатель объекта А и в случае его удаления деструктор попытается во второй раз высвободить одну и ту же память, что приведет к ошибке.
Пускай у нас есть класс ClassName, в котором реализован конструктор и деструктор. А в функции mainсоздадим объект типа ClassName, который присвоим новому объекту того же типа:
Результатом работы программы станет:
Конструктор в этом случае вызовется всего лишь однажды при создании объекта cname. А вот деструктор вызовется для обоих объектов во время завершения работы программы. И хорошо если в объектах нет указателей и динамического выделения памяти. Однако если таковое есть, то выскочит ошибка во время выполнения программы. Столь же неприятные вещи могут происходить при передаче объекта в качестве аргумента в функцию и при возврате объекта из функции.
Передача аргументов в функцию
Как известно, в языках С и С++ по умолчанию аргументы в функцию передаются по значению. При этом происходит создание копии аргумента, которая и участвует во всех выражениях внутри функции, не позволяя измениться переменной, что была передана в данную функцию в качестве аргумента. Копия уничтожается при выходе из области видимости функции. Пускай, к примеру, у нас есть класс ClassNameс конструктором по умолчанию и деструктором; и функция function(), в которую передаем объект типа ClassName:
Конструктор вызывается лишь однажды при создании объекта, тогда как деструктор вызывается дважды — при удалении копии и при удалении самого объекта. При передаче объекта в функцию по значению создается его временная побитовая копия. Если исходный объект имеет поле с указателем, под который выделяется необходимый объем динамической памяти, тогда в его побитовой временной копии также окажется клон-указатель, указывающий на ту же область памяти. При выходе из функции копия объекта уничтожается с высвобождением участка памяти, на который указывает указатель. Но деструктору также приходится в завершение программы уничтожать и сам объект, который мы передаем в функцию, и второй раз высвобождать уже удаленную память.
Возврат объекта из функции
Возврат объекта из функции также создает побитовую временную копию возвращаемого объекта. Для этого необходимо, чтобы функция возвращала некоторый объект, а внутри нее должен быть реализован оператор return, который собственно и возвращает значение объекта. Пускай у нас вновь есть класс ClassNameс реализованным конструктором и деструктором. Также пускай у нас есть функция, не принимающая аргументов, но возвращающая объект типа ClassName.
Результатом работы программы станет:
В данном случае конструктор срабатывает дважды — во время создания объекта cnameи во время создания cname1. Казалось бы, деструктор должен был бы сработать также всего лишь дважды при уничтожении обоих объектов. Однако деструкторов оказалось три, а не два. Так случилось потому, что в функции для возврата формируется временная копия возвращаемого объекта. Именно она должна была бы присвоиться объекту, который принимал бы возвращаемое из функции значение. И именно эту копию уничтожает второй по счету деструктор. Таким образом, мы вновь получаем возможность лишнего указателя на одну и ту же область выделенной памяти, что также приведет к необходимости высвобождения одной и той же памяти дважды.
Конструктор копирования
Чтобы избежать таких сложностей, как правило, в классе реализовывается явный конструктор копирования. Изначально в любом классе присутствуют неявный конструктор копирования, конструктор по умолчанию и деструктор. Однако ценность неявного конструктора копирования невелика по причине его «поверхностной» работы. Чтобы осуществить «глубокое копирование», такой конструктор определяют явно, обозначая параметры копирования. Синтаксис такого конструктора выглядит следующим образом:
Конструктор копирования получает в качестве параметра ссылку на неизменяемый объект. Реализуем этот конструктор в нашем классеClassName, к примеру, для случая с инициализацией объекта.
В этом случае создается объект cname (сообщение «classname_constructor») и его побитовая копия, о чем свидетельствует copy_object. Затем для каждого из объектов вызываются деструкторы. Таким образом, у нас получается инструмент, с помощью которого мы сможем контролировать динамическое выделение памяти и ее высвобождение деструкторами. Если в классе предполагается выделение динамической памяти, тогда в конструкторе копирования не просто происходит присваивание одноименных полей класса, но и очередное выделение динамической памяти с поэлементным копированием значений, находящихся в выделенной памяти. Таким образом, получается два объекта с элементом, указывающим на различные области выделенной памяти, и деструктор будет правильно уничтожать объекты. Такой вид копирования имеет название «глубокого».
Заключение
Необходимость реализации явного конструктора копирования в С++ вытекает сугубо из практических соображений, позволяющих избежать некоторых неудобств при работе с объектами класса. Это инициализация новых объектов класса (при их создании) уже существующими объектами того же класса, передача объектов в качестве аргументов в функцию и возврат объекта из функции. Хотя конструктор копирования и определен по умолчанию в классе, но его функциональности недостаточно для полной реализации, так как в нем определено лишь «поверхностное» копирование. Многие программы требуют более глубокого подхода в своей разработке с применением «глубокого» копирования, для чего конструктор копирования приходится переопределять явно. Надо также сказать, что данный конструктор не применяется в выражениях вида А=В, где используется подход с перегрузкой операции присваивания. Однако он важен именно при инициализации вновь создаваемого объекта.
Если Вам понравилась статья, проголосуйте за нее
Голосов: 18 Голосовать
Конструктор копирования.
Здравствуйте, можете на пальцах объяснить, зачем нужен конструктор копирования? Не совсем ясен его смысл. (
2 ответа 2
Конструктор копирования нужен, чтобы создать копию объекта, причем безопасно. Иногда может сгодится побитовая копия, но в большинстве случаев она не подходит, например когда объект владеет какими-то ресурсами, которые освобождаются в деструкторе. Например если написать такой код:
то один и тот же указатель освободится сначала в деструкторе объекта obj1, а затем в деструкторе объекта obj2, что приведет к ошибке. Правильнее было бы сделать конструктор копирования такой:
Тогда каждый объект будет располагать своим уникальным указателем.
Что бы создать новый объект на основе существующего, тем самым автоматически инициализировав новый объект данными из того, на основе которого делается копия.
Всё ещё ищете ответ? Посмотрите другие вопросы с метками c++ или задайте свой вопрос.
Похожие
Подписаться на ленту
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
дизайн сайта / логотип © 2021 Stack Exchange Inc; материалы пользователей предоставляются на условиях лицензии cc by-sa. rev 2021.12.20.41044
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
Конструктор копирования в С++
Когда новички изучают программирование, первым делом, при рассмотрении новой темы, возникает вопрос – для чего необходима та или иная “вещь” о которой сейчас предстоит узнать. Ответим сразу на этот вопрос: “Зачем нужен конструктор копирования?”.
Конструктор копирования необходим для того, чтобы мы могли создавать “реальные” (а не побитовые) копии для объектов класса. Такая копия объекта может понадобиться в следующих случаях:
При передаче объекта в функцию как параметра по значению, эта функция начнет работать с его побитовой копией, а не с полями самого объекта. Допустим определены конструктор и деструктор класса. Первый память выделяет, а второй её освобождает. Во время работы функции, указатель побитовой копии объекта указывает на адрес памяти, где расположен оригинальный объект.
В то время, когда работа функции завершается – удаляется и побитовая копия объекта. При ее удалении обязательно сработает определённый деструктор и освободит ту память, что занята объектом-оригиналом. Программа продолжит работу, и при завершении работы, деструктор сработает повторно, пытаясь освободить все тот же отрезок памяти. Это вызовет ошибку программы.
Использование конструктора копирования – прекрасный способ обойти эти ошибки и проблемы. Он создаст “реальную” копию объекта, которая будет иметь личную область динамической памяти.
Конструктор копирования синтаксически выглядит так:
Ниже разберём несложный, но очень показательный пример. В нём будут рассмотрены все 3 случая в которых желательно применять конструктор копирования. Будет создан класс, содержащий конструктор без параметров, конструктор копирования и деструктор.
Чтобы пример был не слишком громоздким, конструкторы и деструктор будут выводить на экран сообщения типа “Сработал конструктор”, “Сработал дектруктор”… Выделять и освобождать память не будем.
Нам отлично будет видно сколько раз сработают конструкторы а сколько раз деструктор. Очевидно, что деструктор (если бы он освобождал память) не должен срабатывать большее количество раз, чем конструктор, выделяющий память.
Конструктор без параметров будет вызываться во время создания новых объектов класса. Конструктор копирования – во время создания копий объекта. Деструктор срабатывает при удалении и реального объекта и его копии. В теле функций все описано подробно и не требует дополнительных комментариев.
Запустив программу увидим в консоли следующее:
При выходе из этой функции сработал деструктор, так как копия объекта уничтожается. Кстати, то, что передача объекта как параметра по значению, вызывает конструктор копирования, служит отличным поводом для передачи объекта по ссылке. Это сэкономит и время и память.
Если же мы закомментируем /*конструктор копирования*/ в классе и снова запустим программу – увидим, что конструктор без параметров сработает 2 раза, а деструктор – пять раз отработает.
В этой ситуации, если бы деструктор освобождал память — в программе возникла бы ошибка.