|
В функции-члене можно непосредственно использовать имена членов того объекта, для которого она была вызвана:
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).