Побитовое и почленное копирование в C

Побитовое и почленное копирование в C

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

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

Чтобы продемонстрировать, как работает неглубокое копирование, давайте взглянем на простой класс прямоугольника:

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

Функция main Результат mainРезультат main

Внесенные в rect1 изменения не отражаются на rect2 . Чтобы увидеть проблемы неглубокого копирования, изменим класс Rectangle так, чтобы он содержал указатели:

Измененный класс Rectangle

Выполнение той же функции main выдает другой результат:

Новый результат mainНовый результат main

При изменении rect1 изменилось и содержимое rect2 . Состояние переменных можно выразить с помощью следующей диаграммы:

<span><span><span>Диаграмма, иллюстрирующая поверхностное копирование</span></span></span>Диаграмма, иллюстрирующая поверхностное копирование

Глубокое копирование ещё называют почленным. Чтобы реализовать его для нашего класса, нужно более подробно изучить конструктор копирования и оператор присваивания.

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

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

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

Напомним, что автоматически созданный конструктор копирования выполняет неглубокое копирование. Допустим, класс называется ClassName и имеет поля m 1 , m 2 , m 3 , …, mN . Тогда определение созданного компилятором конструктора выглядит следующим образом:

Конструктор копирования вызывается многократно в разных ситуациях. Самый очевидный случай – когда мы явно создаем новый объект на основе другого экземпляра класса:

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

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

Оператор присваивания – это метод, который используется для выполнения присваивания. Как и в случае с конструктором копирования, C++ предоставляет оператор присваивания по умолчанию.

Пример объявления оператора присваивания:

Оператор присваивания и конструктор копирования реализованы аналогично, хотя есть некоторые заметные различия. Во-первых, мы видим, что оператор присваивания возвращает ссылку на экземпляр, потому что в C ++ разрешены объединенные в цепочку присваивания:

В приведённом выше примере оператор присваивания вызывается для rectangle2 с rectangle3 в качестве аргумента. Затем оператор для rectangle1 вызывается со ссылкой, возвращенной из предыдущего вызова в качестве аргумента. Также необходимо учитывать возможность самоприсваивания:

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

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

  • оператор присваивания;
  • конструктор копирования;
  • деструктор.

Реализация глубокого копирования

Ознакомившись с конструктором копирования и оператором присваивания, мы готовы реализовать глубокое копирование для класса Rectangle .

Конструктор копирования для класса Rectangle выглядит следующим образом:

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

Новый результат mainНовый результат main <span><span><span>Диаграмма, иллюстрирующая глубокое копирование</span></span></span>Диаграмма, иллюстрирующая глубокое копирование

Выводы

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