Глава 17. Конструкторы: копирование и присваивание__________________411
copy) — то, которое предлагается компилятором, и развернутое копирование (deep copy).
Буквальное копирование
Под буквальным копированием понимается поразрядное копирование. Оно означает, что количество и состояние всех битов данных одного объекта точно воспроизводится во втором. Например, точно копируются 32 разряда целого числа; точно так же буквально воспроизводятся и 32 бита указателя
char*.
Как вы знаете, указатель любого типа также состоит из последовательности битов. Значением указателя является адрес (местоположение в памяти). Обычно по этому адресу располагаются какие-то ресурсы, чаще всего просто область памяти. Проблема состоит в том, что эта область не является элементом класса. Поэтому при буквальном копировании дублируется значение указателя, но не то, на что он указывает.
Предположим, указатель задает порт ввода-вывода или адрес некоторой системной области. Например, по адресу 0х00000417 находится слово состояния служебных клавиш (<Shift>, <Caps Lock> и т. п.). Поразрядное копирование указателя такого вида вполне разумно, потому что вашим классам не придется распределять эту область памяти или как-то беспокоиться о ее состоянии — этим занимается BIOS.
К сожалению, чаще всего указатели адресуют участки памяти, выделенные с помощью операции new самой программой. В таком случае буквальное копирование может привести к утечкам памяти. Как ранее уже упоминалось, утечки памяти возникают тогда, когда два или более объекта ссылаются на один и тот же фрагмент памяти и один из них освобождает его, не ставя в известность об этом остальных.
Рис. 17.1 иллюстрирует возникновение утечек памяти. Предположим, что Объект 1 и Объект 2 являются экземплярами одного и того же класса, содержащего указатель на блок памяти, и пусть Объект 2 будет копией Объекта 1.
Легко догадаться, что утечки памяти ни к чему хорошему не приводят. Когда один объект освобождает ресурсы, которые также использует другой объект или объекты, то во втором объекте остаются ненулевые указатели. Когда дело доходит до удаления второго объекта, к его членам указателям применяется операция delete. Вызов delete с указателем в никуда приводит к непредсказуемым, но обычно разрушительным последствиям,
Определяя классы, следует в первую очередь стремиться к тому, чтобы каждый экземпляр был самодостаточен и не вступал с другими объектами того же класса в конфликт из-за ресурсов. Можно придумать механизм для ведения учета ссылок на разделяемый ресурс и управления освобождением этого ресурса, но это совершенно отдельная тема.