Когда вызывается конструктор копирования c

от admin

Что такое конструктор копирования в C++?

Что такое конструктор копирования в C++ и для чего он нужен? Что можно сделать с помощью конструктора копирования, почему говорят, что лучше не использовать конструкторы копирования, и что такое явный и неявный конструктор копирования?

aleksandr barakin's user avatar

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

Конструктор копирования вызывается в случаях, когда нужно получить полную копию объекта. В C++ полная копия объекта нужна в трех случаях.

Случай 1. В момент объявления нового объекта и его инициализации данными другого объекта с помощью оператора =. Следующий фрагмент кода демонстрирует данную ситуацию для некоторого класса ClassName

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

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

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

HolyBlackCat's user avatar

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

тип AA имеет n и, i своего базового типа.

Объекты типа А создаем, копируя данные ранее созданных объектов. При копировании производных объектов, происходит срезка его данных, то есть копируется только базовая часть( в данном случаи aa1.i и aa2.i).

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

Чтобы не выполнить лишнее копирование, в случаях, когда нет необходимости создания нового объекта не встроенных типов, лучше просто ссылаться на них (A& refА = a или const A& crefА = a, если не нужно модифицировать объект).

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

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

С++. Конструктор копирования. Примеры использования. Передача объекта класса в функцию. Возврат класса из функции

В данной теме рассмотрена работа конструктора копирования на примере unmanaged ( native ) классов. При рассмотрении данной темы рекомендуется прочитать тему:

Содержание

  • 1. Какое назначение конструктора копирования (copy constructor)?
  • 2. В каких случаях вызывается конструктор копирования?
  • 3. В каких случаях целесообразно использовать конструктор копирования?
  • 4. Пример объявления конструктора копирования в классе, где нет динамического выделения памяти
  • 5. Пример передачи объекта класса в функцию как параметр-значение
  • 6. Пример возврата объекта класса из функции по значению с помощью конструктора копирования
  • Связанные темы

Поиск на других ресурсах:

1. Какое назначение конструктора копирования ( copy constructor )?

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

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

Конструктор копирования вызывается в случаях, когда нужно получить полную копию объекта. В C++ полная копия объекта нужна в трех случаях.

Случай 1. В момент объявления нового объекта и его инициализации данными другого объекта с помощью оператора = . Следующий фрагмент кода демонстрирует данную ситуацию для некоторого класса ClassName

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

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

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

3. В каких случаях целесообразно использовать конструктор копирования?

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

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

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

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

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

Демонстрация использования конструктора копирования в некотором программном коде (методе)

5. Пример передачи объекта класса в функцию как параметр-значение

В примере передается объект класса CMyPoint (см. п. 4) в функцию GetLength() , которая вычисляет расстояние от точки CMyPoint к началу координат. Текст функции следующий:

Использование функции в другом программном коде

6. Пример возврата объекта класса из функции по значению с помощью конструктора копирования

Реализовать функцию GetCenterPoint() , которая возвращает точку, которая есть серединой отрезка, проведенного между точкой CMyPoint и началом координат.

Объявление класса точно такое же как в п. 4.

Реализация двух вариантов функций GetCenterPoint() и GetCenterPoint2() .

Демонстрация использования функций

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

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

7. Как осуществляется копирование объектов, когда в классе отсутствует конструктор копирования?

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

Читать:
Регулярное выражение python как исключить символы

Побитовое копирование есть приемлемым для классов, в которых нет динамического выделения памяти. Однако, если в классе есть динамическое выделение памяти (класс использует указатели), то побитовое копирование приведет к тому, что указатели обоих объектов будут указывать на один и тот же участок памяти. А это ошибка.

Урок №141. Конструктор копирования

Вспомним все типы инициализации, которые поддерживает язык C++: прямая инициализация, uniform-инициализация и копирующая инициализация.

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

Рассмотрим примеры всех вышеприведенных инициализаций на практике, используя следующий класс Drob:

Мы можем выполнить прямую инициализацию:

В C++11 мы можем выполнить uniform-инициализацию:

И, наконец, мы можем выполнить копирующую инициализацию:

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

Рассмотрим следующую программу:

Результат выполнения программы:

Рассмотрим детально, как работает эта программа.

С объектом sixSeven выполняется обычная прямая инициализация, которая приводит к вызову конструктора Drob(int, int) . Здесь нет никаких сюрпризов. А вот инициализация объекта dCopy также является прямой инициализацией, но какой конструктор вызывается здесь? Ответ: конструктор копирования.

Конструктор копирования — это особый тип конструктора, который используется для создания нового объекта через копирование существующего объекта. И, как в случае с конструктором по умолчанию, если вы не предоставите конструктор копирования для своих классов самостоятельно, то язык C++ создаст public-конструктор копирования автоматически. Поскольку компилятор мало знает о вашем классе, то по умолчанию созданный конструктор копирования будет использовать почленную инициализацию. Почленная инициализация означает, что каждый член объекта-копии инициализируется непосредственно из члена объекта-оригинала. Т.е. в примере, приведенном выше, dCopy.m_numerator будет иметь значение sixSeven.m_numerator ( 6 ), а dCopy.m_denominator будет равен sixSeven.m_ denominator ( 7 ).

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

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

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

Вот примеры всех их с использованием нашего класса Fraction :

Мы можем выполнить прямую инициализацию:

В C++11 мы можем выполнить унифицированную инициализацию:

И наконец, мы можем выполнить копирующую инициализацию:

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

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

Теперь рассмотрим следующую программу:

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

Давайте подробнее рассмотрим, как она работает.

Инициализация переменной fiveThirds – это просто стандартная прямая инициализация, которая вызывает конструктор Fraction(int, int) . Никаких сюрпризов. А как насчет следующей строки? Инициализация переменной fCopy также явно является прямой инициализацией, и вы знаете, что функции-конструкторы используются для инициализации классов. Итак, какой конструктор вызывает эта строка?

Ответ заключается в том, что эта строка вызывает конструктор копирования Fraction . Конструктор копирования – это особый тип конструктора, используемый для создания нового объекта как копии существующего объекта. И так же, как конструктор по умолчанию, если вы не предоставляете конструктор копирования для своих классов, C++ создаст для вас открытый (public) конструктор копирования. Поскольку компилятор мало что знает о вашем классе, созданный по умолчанию конструктор копирования использует метод инициализации, называемый поэлементной инициализацией. Поэлементная инициализация просто означает, что каждый член копии инициализируется напрямую членом копируемого класса. В приведенном выше примере fCopy.m_numerator будет инициализирован из fiveThirds.m_numerator и т.д.

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

При запуске этой программы вы получите:

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

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

Одно интересное замечание: вы уже видели несколько примеров перегруженного оператора operator<< , в котором мы можем получить доступ к закрытым членам параметра f1 , потому что функция является другом класса Fraction . Точно так же функции-члены класса могут обращаться к закрытым членам параметров одного и того же типа класса. Поскольку наш конструктор копирования Fraction принимает параметр типа класса (для создания копии), мы можем напрямую обращаться к членам параметра дроби Fraction , даже если это не неявный объект.

Предотвращение копирования

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

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

Конструктор копирования может быть опущен

Теперь рассмотрим следующий пример:

Рассмотрим, как работает эта программа. Сначала мы напрямую инициализируем анонимный объект Fraction , используя конструктор Fraction(int, int) . Затем мы используем этот анонимный объект Fraction в качестве инициализатора для Fraction fiveThirds . Поскольку анонимный объект является Fraction , как и fiveThirds , это должно вызывать конструктор копирования, верно?

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

Но на самом деле у вас больше шансов получить такой результат:

Почему не был вызван наш конструктор копирования?

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

По этой причине в таких случаях компилятору разрешено отказаться от вызова конструктора копирования и вместо этого просто выполнить прямую инициализацию. Этот процесс называется элизией (исключением).

Итак, хотя вы написали:

Компилятор может изменить это на:

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

До C++17 исключение копирования – это оптимизация, которую может сделать компилятор. Начиная с C++17, некоторые случаи исключения копирования (включая приведенный выше пример) стали обязательными.

Наконец, обратите внимание, что если вы сделаете конструктор копирования закрытым, любая инициализация, которая будет использовать конструктор копирования, вызовет ошибку компиляции, даже если конструктор копирования опущен!

Related Posts