С++ для начинающих


Неявный указатель this


У каждого объекта класса есть собственная копия данных-членов. Например:

int main() {

   Screen myScreen( 3, 3 ), bufScreen;

   myScreen.clear();

   myScreen.move( 2, 2 );

   myScreen.set( '*' );

   myScreen.display();

   bufScreen.resize( 5, 5 );

   bufScreen.display();

}

У объекта myScreen есть свои члены _width, _height, _cursor и _screen, а у объекта bufScreen – свои. Однако каждая функция-член класса существует в единственном экземпляре. Их и вызывают myScreen и bufScreen.

В предыдущем разделе мы видели, что функция-член может обращаться к членам своего класса, не используя операторы доступа. Так, определение функции move() выглядит следующим образом:

inline void Screen::move( int r, int c )



{

   if ( checkRange( r, c ) )      // позиция на экране задана корректно?

   {

      int row = (r-1) * _width;   // смещение строки

      _cursor = row + c - 1;

   }

}

Если функция move() вызывается для объекта myScreen, то члены _width и _height, к которым внутри нее имеются обращения, – это члены объекта myScreen. Если же она вызывается для объекта bufScreen, то и обращения производятся к членам данного объекта. Каким же образом _cursor, которым манипулирует move(), оказывается членом то myScreen, то bufScreen? Дело в указателе this.

Каждой функции-члену передается указатель на объект, для которого она вызвана, – this. В неконстантной функции-члене это указатель на тип класса, в константной – константный указатель на тот же тип, а в функции со спецификатором volatile указатель с тем же спецификатором. Например, внутри функции-члена move() класса Screen указатель this имеет тип Screen*, а в неконстантной функции-члене List – тип List*.

Поскольку this адресует объект, для которого вызвана функция-член, то при вызове move() для myScreen он указывает на объект myScreen, а при вызове для bufScreen – на объект bufScreen. Таким образом, член _cursor, с которым работает функция move(), в первом случае принадлежит объекту myScreen, а во втором – bufScreen.


Понять все это можно, если представить себе, как компилятор реализует объект this. Для его поддержки необходимо две трансформации:

1.      Изменить определение функции-члена класса, добавив дополнительный параметр:

// псевдокод, показывающий, как происходит расширение

// определения функции-члена

// ЭТО НЕ КОРРЕКТНЫЙ КОД C++

inline void Screen::move( Screen *this, int r, int c )

{

   if ( checkRange( r, c ) )

   {

      int row = (r-1) * this->_width;

      this->_cursor = row + c - 1;

   }

}

В этом определении использование указателя this для доступа к членам _width и _cursor сделано явным.

2.      Изменение каждого вызова функции-члена класса с целью передачи одного дополнительного аргумента – адреса объекта, для которого она вызвана:

myScreen.move( 2, 2 );

транслируется в

move( &myScreen, 2, 2 );

Программист может явно обращаться к указателю this внутри функции. Так, вполне корректно, хотя и излишне, определить функцию-член home() следующим образом:

inline void Screen::home()

{

   this->_cursor = 0;

}

Однако бывают случаи, когда без такого обращения не обойтись, как мы видели на примере функции-члена copy() класса Screen. В следующем подразделе мы рассмотрим и другие примеры.


Содержание раздела