|
Программирование >> Аргументация конструирования
int length = strlen(pN) + 1; pName = new char[length]; strncpy(pName, pN, length); eName - освобождение памятно адресу pName void deleteNameO если есть этот блок памяти... if (pName) вернуть его куче.. . delete pName; ...и отметить, что этот указатель более недоступен рЫ ате = О; pName указывает ка блок памяти, содержащий имя в виде строки в формате ASCIIZ char *pName; in t argcs, char* pArgs [ ] ) ( Name s( Claudette ) ; Name t( temporary ) ; = 5; Здесь вызывается оператор присвоения return 0; Класс Name хранит имя человека в памяти, которая выделена из кучи конструктором. Конструктор и деструктор Name типичны для класса, содержащего динамический массив памяти. Оператор присвоения имеет вид operator= {). Заметьте, что оператор присвоения как бы состоит из деструктора со следующим за ним копирующим конструктором. Это тоже типично. Разберем операцию присвоения в приведенном примере. Объект t содержит связанное с ним имя (temporary). Выполняя присвоение t вы сначала должны вызвать функцию deleteName (), чтобы освободить память, которую занимало предыдущее имя, и только после этого для выделения новой памяти, в которую будет записано новое имя, вы можете вызвать функцию copyName (). В копирующем конструкторе не нужен вызов deleteName (), поскольку в момент вызова конструктора объекта еще не существует, а значит, память для размещения имени еще не выделена. Обычно оператор присвоения состоит из двух частей. Первая схожа с деструктором в плане освобождения ресурсов, которыми владеет данный объект. Вторая часть схожа с конструктором копирования возможностью выделения новых ресурсов для копирования исходного объекта в целевой. Глубокая проблема создания мелких копий Попробуем разобраться, чем же нам не угодило обычное почленное копирование? И в самом деле, поверхностного копирования зачастую бывает вполне достаточно, но, например, в случае с Name это не так. Класс Name содержит ресурсы, созданные специально для использования в этом объекте, а именно блок памяти, на который указывает Это легко увидеть, по- смотрев код конструкторов класса. Каждый из них (кроме конструктора по умолчанию) вызывает функцию copyName (). copyName - копирует исходную строку pN в локально выделенный блок памяти voici copyName (char pN) int length = strlen(pN) + 1; pName = new char[length]; strncpy(pName, pN, length); Обратите внимание на то, как функция выделяет место в памяти иод строку, равную длине исходной, а затем копирует содержимое pN в созданную строку. При почленном копировании получается два объекта класса Name, указывающих на один и тот же фрагмент в памяти, а потому использование этого метода может легко привести к большим проблемам (например, что произойдет при удалении одного из объектов?). Оператор присвоения класса во избежание утечки памяти сначала должен ос- вободить блок, на который указывает рКате (с помощью вызова функции deleteName ()), а затем запросить новый фрагмент с помощью функции соруКате (). Такой метод и называется глубоким копированием. Почленный подход К С Заметьте, что я б1л очень точен и всегда говорил почленное копирование . Я избегал упрощенного и термина копирование , поскольку он означает примитивное побитовое копирование. Смысл почленного копирования не очевиден, пока вы не обратитесь к классам, которые содержат в качестве членов объекты другого class MyClass { public : Name name; int age; MyClass (char* pName, in Age) (pName ) age = newAge; void fnO MyClass a ( Kinsey , 16); MyClass bCChrista , 1); a = b; (Если вы до сих пор незнакомы с синтаксисом инициализации : nam Ште), самое время вернуться к главе 18, Аргументация конструирования .) В принципе в случае, когда новое имя не длиннее старого, мы могли бы обойтись без освобождения памяти и выделения новой, просто перезаписав новое имя на место старого (ценой этого упрощения было бы несколько неэкономное использование памяти программой).-1Примм..ред. Оператор присвоения по умолчанию в данном случае отлично работает. Дело в том, что для копирования члена name используется оператор Name: : operator={). Отсюда правило: хорошего пива... если класс сам по себе не распределя- ет ресурсы (независимо от того, делают ли это его члены), то нет и необходимости в перегрузке оператора присвоения. Возврат результата присвоения Обратите внимание: возвращаемое значение функции operator=() имеет тип Names. Я мог бы сделать этот тип void - C+ + не возражал бы против этого, но тогда не работал бы приведенный ниже код. void OtherFn(Nameu); void fn(Karnes oldH) Name newN; УЕЬ:, ЭТОТ КОД не работает. . . OtherFn(newN = oldW); ... так же, как и этот... Name newerN; newei-N = newN = oldN; Результат присвоения newN = oldN имел бы в этом случае тип void, а значит, никак не мог бы использоваться. Помните, что результат выполнения оператора присвоения - это значение правого аргумента с типом левого. Таким образом, значение выражеНИЯ (i = 1) равно I. Именно благодаря этому выражения типа i = j = 1; полны смысла в С+ + . Переменной i присваивается результат присвоения j = 1, который равен 1. Объявление функции operator=() как возвращающей сс1лку на текущий объект * this оставляет неизменной это семантическое правило С + + . Вторая деталь, на которую стоит обратить внимание: operator=() должен быть объявлен как функция-член. Оператор присвоения, в отличие от других операторов, не может быть перегружен с помощью функции - не члена класса. Оператор присвоения должен быть нестатической функцией-членом класса. Интересно, что при этом нет никаких ограничений на специальные операторы типа += или *=; эти операторы могут быть функциями - не членами. Защш£а членов Написать оператор присвоения не очень трудно, тем не менее иногда возможны ситуации, когда по тем или иным соображениям он не нужен. Если вы действительно хотите этого, можете сделать выполнение присвоения невозможным, перегрузив оператор по умолчанию защищенным оператором присвоения, например, так: class Name .. .то же, что и раньше... protected:
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |