Опубликован: 10.10.2006 | Доступ: свободный | Студентов: 6514 / 583 | Оценка: 4.26 / 3.88 | Длительность: 31:30:00
Лекция 5:

Классы

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:
 int readme() const { return m; }
 int 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:
 int readme() const;
 int 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 нужно указывать как в описании, так и в определении постоянной функции-члена. Физическое постоянство обеспечивается помещением объекта в защищенную по записи память, только если в классе нет конструктора.

Равиль Ярупов
Равиль Ярупов
Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?