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;
Бьерн Страуструп.
Язык программирования С++
125
и инициализируется, чтобы указывать на объект, для которого функция-член вызывалась. Этот
указатель нельзя изменять, поскольку он постоянный (*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);
//
ошибка
}
Бьерн Страуструп.
Язык программирования С++
126
В этом примере разумный транслятор смог бы обнаружить, что функция 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 {
Бьерн Страуструп.
Язык программирования С++
127
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).
Достарыңызбен бөлісу: |