|
Программирование >> Поддержка объектно-ориентированного программирования
В этом примере разумный транслятор смог бы обнаружить, что функция X::writeme() пытается изменить постоянный объект. Однако, это непростая задача для транслятора. Из-за раздельной трансляции он в общем случае не может гарантировать постоянство объекта, если нет соответствующего описания со спецификацией const. Например, определения readme() и writeme() могли быть в другом файле: class X { int m; public: readme() const; writeme(int i); В таком случае описание readme() со спецификацией const существенно. Тип указателя this в постоянной функции-члене класса X есть const X *const. Это значит, что без явного приведения с помощью this нельзя изменить значение объекта: class X { int m; public: ... void implicit cheat() const { m++; } ошибка void explicit cheat() const { ((X*)this)->m++; } нормально Отбросить спецификацию const можно потому, что понятие постоянства объекта имеет два значения. Первое, называемое физическим постоянством состоит в том, что объект хранится в защищенной от записи памяти. Второе, называемое логическим постоянством заключается в том, что объект выступает как постоянный (неизменяемый) по отношению к пользователям. Операция над логически постоянным объектом может изменить часть данных объекта, если при этом не нарушается его постоянство с точки зрения пользователя. Операциями, ненарушающими логическое постоянство объекта, могут быть буферизация значений, ведение статистики, изменение переменных-счетчиков в постоянных функциях-членах. Логического постоянства можно достигнуть приведением, удаляющим спецификацию const: class calculator1 { int cache val; int cache arg; ... public: int compute(int i) const; ... int calculator1::compute(int i) const if (i == cache arg) return cache val; нелучший способ ((calculator1*)this)->cache arg = i; ((calculator1*)this)->cache val = val; return val; Этого же результата можно достичь, используя указатель на данные без const: struct cache { int val; int arg; class calculator2 { public: int compute(int i) const; int calculator2::compute(int i) const if (i == p->arg) return p->val; нелучший способ p->arg = i; p->val = val; return val; Отметим, что const нужно указывать как в описании, так и в определении постоянной функции-члена. Физическое постоянство обеспечивается помещением объекта в защищенную по записи память, только если в классе нет конструктора ($$7.1.6). 5.2.4 Инициализация Инициализация объектов класса с помощью таких функций как set date() - неэлегантное и чреватое ошибками решение. Поскольку явно не было указано, что объект требует инициализации, программист может либо забыть это сделать, либо сделать дважды, что может привести к столь же катастрофическим последствиям. Лучше дать программисту возможность описать функцию, явно предназначенную для инициализации объектов. Поскольку такая функция конструирует значение данного типа, она называется конструктором. Эту функцию легко распознать - она имеет то же имя, что и ее класс: class date { ... date(int, int, int); Если в классе есть конструктор, все объекты этого класса конструктору требуются параметры, их надо указывать: date today = date(23,6,1983); date xmas(25,12,0); date my birthday; будут проинициализированы. Если краткая форма неправильно, нужен инициализатор Часто бывает удобно указать несколько способов инициализации объекта. Для этого нужно описать несколько конструкторов: class date { int month, day, year; public: date(int, int, int); date(int, int); date(int); date(); date(const char*); день, месяц, год день , месяц и текущий год день и текущие год и месяц стандартное значение: текущая дата дата в строковом представлении Параметры конструкторов подчиняются тем же правилам о типах параметров, что и все остальные функции ($$4.6.6). Пока конструкторы достаточно различаются по типам своих параметров, транслятор способен правильно выбрать конструктор: date today(4); date july4( July 4, 1983 ); date guy( 5 Nov ); cache* p; date now; инициализация стандартн1м значением Размножение конструкторов в примере c date типично. При разработке класса всегда есть соблазн добавить еще одну возможность, - а вдруг она кому-нибудь пригодится. Чтобы определить действительно нужные возможности, надо поразмышлять, но зато в результате, как правило, получается более компактная и понятная программа . Сократить число сходных функций можно с помощью стандартного значения параметра. В примере с date для каждого параметра можно задать стандартное значение, что означает: взять значение из текущей даты . class date { int month, day, year; public: ... date(int d =0, int m =0, y=0); ... date::date(int d, int m, int y) day = d ? d : today.day; month = m ? m : today.month; year = y ? y : today.year; проверка правильности даты ... Когда используется стандартное значение параметра, оно должно отличаться от всех допустимых значений параметра. В случае месяца и дня очевидно, что при значении нуль - это так, но неочевидно, что нуль подходит для значения года. К счастью, в европейском календаре нет нулевого года, т.к. сразу после 1 г. до р.х. (year==-1) идет 1 г. р.х. (year==1). Однако для обычной программы это, возможно, слишком тонкий момент. Объект класса без конструктора может инициализироваться присваиванием ему другого объекта этого же класса. Это незапрещено и в том случае, когда конструкторы описаны: date d = today; инициализация присваиванием На самом деле, имеется стандартный конструктор копирования, определенный как поэлементное копирование объектов одного класса. Если такой конструктор для класса X не нужен, можно переопределить его как конструктор копирования X::X(const X&). Подробнее поговорим об этом в $$7.6. 5.2.5 Удаление Пользовательские типы чаще имеют, чем не имеют, конструкторы , которые проводят надлежащую инициализацию. Для многих типов требуется и обратная операция - деструктор, гарантирующая правильное удаление объектов этого типа. Деструктор класса X обозначается ~X ( дополнение конструктора ). В частности, для многих классов используется свободная память (см. $$3.2.6), выделяемая конструктором и освобождаемая деструктором. Вот, например, традиционное определение типа стек, из которого для краткости полностью выброшена обработка ошибок: class char stack { int size; char* top; char* s; public: char stack(int sz) { top=s=new char[size=sz]; } ~char stack() { delete[] s; } деструктор void push(char c) { *top++ = c; } void pop() { return *--top; } Когда объект типа char stack выходит из текущей области видимости, вызывается деструктор:
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |