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

  Юрий  | 

    | 

  Обновл. 15 Июл 2019  | 

 18437

 ǀ   9 

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

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

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

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

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

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

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

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

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

6/7

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

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

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

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

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

Copy constructor worked here!
6/7

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

Предотвращение создания копий объектов


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

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

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

Рассмотрим следующий код:

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

Запустите эту программу самостоятельно. Ожидаемый результат:

Copy constructor worked here!
6/7

Реальный результат:

6/7

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

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

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

Поэтому, даже если вы напишите:

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

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

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


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (50 оценок, среднее: 4,86 из 5)
Загрузка...

Комментариев: 9

  1. Аватар Алексей:

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

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

    пусть есть функции определения расстояния между точками в виде:

    ну и функция вывода координат точек:

    теперь использую класс:

    и хочу воспользоваться функцией вычисления расстояния, вызывая её так:

    1. Аватар Валерий:

      Запишите в список благодарящих и меня.
      Но…. Добавлю немного критики.
      Если открыта возможность комментировать и комментарии проходят модерацию, хотелось бы видеть комментарии хотя бы не содержащие явных ошибок. На приведенный выше код компилятор матерится. Отсутствующие ;
      Запятые вместо меньше.

      Так ac или bc?

      По самому примеру.
      1. Кто гарантирует возможность работы с набором отдельных переменных как с массивом? Где гарантия, что отдельные переменные будут располагаться подряд и не будет по каким-то причинам использовано выравнивание по адресам? Где гарантия, что компилятор не оптимизирует распределение памяти?
      2. Перефразирую начало. Я хочу использовать классы, но использовать классы не хочу.

      Хотя в целом мысль идет в правильном направлении.

      1. Аватар Валерий:

        И сам ошибся 🙂

        Так ac или ab?
        в названии переменной — ac а вызывается a и b.

      2. Аватар Алексей:

        Возможность обращения как к массиву и строго последовательное размещение в памяти при описании одним оператором, именно в таком виде- double x,y,z; гарантируется стандартом языка.
        Читал про это очень давно, ещё для языка с. Для с++ то же самое вытекает из правила "любая корректная программа на с является корректной на с++". Неоднократно встречал случаи такого использования в реальных программах и применял сам, проблем никогда не было.

  2. Аватар Андрей:

    может еще описать способ =delete? или если есть уже такая тема, указать ссылку на него( раз способ закрытия конструктора копирования указывается в этой теме)

  3. Аватар Crafty Fox:

    Спасибо тебе большое за этот титанический труд по переводу! Уроки действительно очень хорошие и легко читаются. Надеюсь, у тебя хватит сил и терпению перевести их до конца. Думаю, они многим помогут выучить С++.

    1. Юрий Юрий:

      Спасибо за отзыв. Надеюсь, что сил и терпения действительно хватит 🙂

      1. Аватар Илья:

        Всячески присоединяюсь к предыдущему комментарию! Спасибо, Юрий, огромное! Тяжелый труд — великолепный результат — безграничная благодарность тебе!

        1. Юрий Юрий:

          Спасибо и Вам, что читаете 🙂

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *