Мы определили несколько функций для работы со структурой date, но из ее
описания не следует, что это единственные функции, которые
предоставляют доступ к объектам типа date. Можно установить такое
ограничение, описав класс вместо структуры:
class date {
int month, day, year;
public:
void set(int, int, int);
void get(int*, int*, int*);
void next();
void print()
};
Служебное слово public (общий) разбивает описание класса на две части.
Имена, описанные в первой частной (private) части класса, могут
использоваться только в функциях-членах. Вторая - общая часть -
представляет собой интерфейс с объектами класса. Поэтому структура - это
такой класс, в котором по определению все члены являются общими.
Функции-члены класса определяются и используются точно так же, как
было показано в предыдущем разделе:
void date::print() // печать даты в принятом в США виде
{
cout << month << '/' << day << '/' << year ;
}
Однако от функций не членов частные члены класса date уже ограждены:
void backdate()
{
today.day--; // ошибка
}
Есть ряд преимуществ в том, что доступ к структуре данных ограничен
явно указанным списком функций. Любая ошибка в дате (например,
December, 36, 1985) могла быть внесена только функцией-членом,
поэтому первая стадия отладки - локализация ошибки - происходит
даже до первого пуска программы. Это только частный случай общего
правила: любое изменение в поведении типа date может и должно
вызываться изменениями в его членах. Другое преимущество в том, что
потенциальному пользователю класса для работы с ним достаточно
знать только определения функций-членов.
Защита частных данных основывается только на ограничении
использования имен членов класса. Поэтому ее можно обойти с
помощью манипуляций с адресами или явных преобразований типа,
но это уже можно считать мошенничеством.
5.2.3 Ссылка на себя
В функции-члене можно непосредственно использовать имена членов
того объекта, для которого она была вызвана:
class X {
int m;
public:
int readm() { return m; }
};
void f(X aa, X bb)
{
int a = aa.readm();
int b = bb.readm();
// ...
}
При первом вызове readm() m обозначает aa.m, а при втором - bb.m.
У функции-члена есть дополнительный скрытый параметр, являющийся
указателем на объект, для которого вызывалась функция. Можно явно
использовать этот скрытый параметр под именем this. Считается, что
в каждой функции-члене класса X указатель this описан неявно как
X *const this;
и инициализируется, чтобы указывать на объект, для которого
функция-член вызывалась. Этот указатель нельзя изменять, поскольку
он постоянный (*const). Явно описать его тоже нельзя, т.к. this -
это служебное слово. Можно дать эквивалентное описание класса X:
class X {
int m;
public:
int readm() { return this->m; }
};
Для обращения к членам использовать this излишне. В основном this
используется в функциях-членах, непосредственно работающих с
указателями. Типичный пример - функция, которая вставляет элемент
в список с двойной связью:
class dlink {
dlink* pre; // указатель на предыдущий элемент
dlink* suc; // указатель на следующий элемент
public:
void append(dlink*);
// ...
};
void dlink::append(dlink* p)
{
p->suc = suc; // т.е. p->suc = this->suc
p->pre = this; // явное использование "this"
suc->pre = p; // т.е. this->suc->pre = p
suc = p; // т.е. this->suc = p
}
dlink* list_head;
void f(dlink* a, dlink* b)
{
// ...
list_head->append(a);
list_head->append(b);
}
Списки с такой общей структурой служат фундаментом списочных классов,
описываемых в главе 8. Чтобы присоединить звено к списку, нужно
изменить объекты, на которые настроены указатели this, pre и suc.
Все они имеют тип dlink, поэтому функция-член dlink::append() имеет
к ним доступ. Защищаемой единицей в С++ является класс, а не отдельный
объект класса.
Можно описать функцию-член таким образом, что объект, для которого
она вызывается, будет доступен ей только по чтению. Тот факт, что
функция не будет изменять объект, для которого она вызывается
(т.е. this*), обозначается служебным словом const в конце списка
параметров:
class X {
int m;
public:
readme() const { return m; }
writeme(int i) { m = i; }
};
Функцию-член со спецификацией const можно вызывать для постоянных
объектов, а функцию-член без такой спецификации - нельзя:
void f(X& mutable, const X& constant)
{
mutable.readme(); // нормально
mutable.writeme(7); // нормально
constant.readme(); // нормально
constant.writeme(7); // ошибка
}
В этом примере разумный транслятор смог бы обнаружить, что
функция 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 {
cache* p;
// ...
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).
Достарыңызбен бөлісу: |