13.6.1. Тип члена класса

We use cookies. Read the Privacy and Cookie Policy

13.6.1. Тип члена класса

Указателю на функцию нельзя присвоить адрес функции-члена, даже если типы возвращаемых значений и списки параметров полностью совпадают. Например, переменная pfi – это указатель на функцию без параметров, которая возвращает значение типа int:

int (*pfi)();

Если имеются глобальные функции HeightIs() и WidthIs() вида:

int HeightIs();

int WidthIs();

то допустимо присваивание pfi адреса любой из этих переменных:

pfi = HeightIs;

pfi = WidthIs;

В классе Screen также определены две функции доступа, height() и width(), не имеющие параметров и возвращающие значение типа int:

inline int Screen::height() { return _height; }

inline int Screen::width() { return _width; }

Однако попытка присвоить их переменной pfi является нарушением типизации и влечет ошибку компиляции:

// неверное присваивание: нарушение типизации

pfi = &Screen::height;

В чем нарушение? У функций-членов есть дополнительный атрибут типа, отсутствующий у функций, не являющихся членами, – класс. Указатель на функцию-член должен соответствовать типу присваиваемой ему функции не в двух, а в трех отношениях: по типу и количеству формальных параметров; типу возвращаемого значения; типу класса, членом которого является функция.

Несоответствие типов между двумя указателями – на функцию-член и на обычную функцию – обусловлено их разницей в представлении. В указателе на обычную функцию хранится ее адрес, который можно использовать для непосредственного вызова. (Указатели на функции рассматривались в разделе 7.9.) Указатель же на функцию-член должен быть сначала привязан к объекту или указателю на объект, чтобы получить this, и только после этого он применяется для вызова функции-члена. (В следующем подразделе мы покажем, как осуществить такую привязку.) Хотя для указателя на обычную функцию и для указателя на функцию-член используется один и тот же термин, их природа различна.

Синтаксис объявления указателя на функцию-член должен принимать во внимание тип класса. То же верно и в отношении указателей на данные-члены. Рассмотрим член _height класса Screen. Его полный тип таков: член класса Screen типа short. Следовательно, полный тип указателя на _height – это указатель на член класса Screen типа short:

short Screen::*

Определение указателя на член класса Screen типа short выглядит следующим образом:

short Screen::*ps_Screen;

Переменную ps_Screen можно инициализировать адресом _height:

short Screen::*ps_Screen = &Screen::_height;

или присвоить ей адрес _width:

short Screen::*ps_Screen = &Screen::_width;

Переменной ps_Screen разрешается присваивать указатель на _width или _height, так как они являются членами класса Screen типа short.

Несоответствие типов указателя на данные-члены и обычного указателя также связано с различием в их представлении. Обычный указатель содержит всю информацию, необходимую для обращения к объекту. Указатель на данные-члены следует сначала привязать к объекту или указателю на него, а лишь затем использовать для доступа к члену этого объекта. (В книге “Inside the C++ Object Model” ([LIPPMAN96a]) также описывается представление указателей на члены.)

Указатель на функцию-член определяется путем задания типа возвращаемого функцией значения, списка ее параметров и класса. Например, следующий указатель, с помощью которого можно вызвать функции height() и width(), имеет тип указателя на функцию-член класса Screen без параметров, которая возвращает значение типа int:

int (Screen::*)()

Указатели на функции-члены можно объявлять, инициализировать и присваивать:

// всем указателям на функции-члены класса можно присвоить значение 0

int (Screen::*pmf1)() = 0;

int (Screen::*pmf2)() = &Screen::height;

pmf1 = pmf2;

pmf2 = &Screen::width;

Использование typedef может облегчить чтение объявлений указателей на члены. Например, для типа “указатель на функцию-член класса Screen без параметров, которая возвращает ссылку на объект Screen”, т.е.

Screen& (Screen::*)()

Следующий typedef определяет Action как альтернативное имя:

typedef Screen& (Screen::*Action)();

Action default = &Screen::home;

Action next = &Screen::forward;

Тип "указатель на функцию-член" можно использовать для объявления формальных параметров и типа возвращаемого значения функции. Для параметра того же типа можно также указать значение аргумента по умолчанию:

Screen& action( Screen&, Action)();

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

Screen meScreen;

typedef Screen& (Screen::*Action)();

Action default = &Screen::home;

extern Screen& action( Screen&, Sction = &Screen::display );

void ff()

{

action( myScreen );

action( myScreen, default );

action( myScreen, &Screen::end );

}

В следующем подразделе обсуждается вызов функции-члена посредством указателя.

13.6.2. Работа с указателями на члены класса

К указателям на члены класса можно обращаться только с помощью конкретного объекта или указателя на объект типа класса. Для этого применяется любой из двух операторов доступа (.* для объектов класса и ссылок на них или -* для указателей). Например, так вызывается функция-член через указатель на нее:

int (Screen::*pmfi)() = &Screen::height;

Screen& (Screen::*pmfS)( const Screen& ) = &Screen::copy;

Screen myScreen, *bufScreen;

// прямой вызов функции-члена

if ( myScreen.height() == bufScreen-height() )

bufScreen-copy( myScreen );

// эквивалентный вызов по указателю

if ( (myScreen.*pmfi)() == (bufScreen-*pmfi)() )

(bufScreen-*pmfS)( myScreen );

Вызовы

(myScreen.*pmfi)()

(bufScreen-*pmfi)();

требуют скобок, поскольку приоритет оператора вызова () выше, чем приоритет взятия указателя на функцию-член. Без скобок

myScreen.*pmfi()

интерпретируется как

myScreen.*(pmfi())

Это означает вызов функции pmfi() и привязку возвращенного ей значения к оператору (.*). Разумеется, тип pmfi не поддерживает такого использования, так что компилятор выдаст сообщение об ошибке.

Указатели на данные-члены используются аналогично:

typedef short Screen::*ps_Screen;

Screen myScreen, *tmpScreen = new Screen( 10, 10 );

ps_Screen pH = &Screen::_height;

ps_Screen pW = &Screen::_width;

tmpScreen-*pH = myScreen.*pH;

tmpScreen-*pW = myScreen.*pW;

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

typedef Screen& (Screen::Action)();

Screen& Screen::repeat( Action op, int times )

{

for ( int i = 0; i

Параметр op – это указатель на функцию-член, которая должна вызываться times раз.

Если бы нужно было задать значения аргументов по умолчанию, то объявление repeat() выглядело бы следующим образом:

class Screen {

public:

Screen &repeat( Action = &Screen::forward, int = 1 );

// ...

};

А ее вызовы так:

Screen myScreen;

myScreen.repeat(); // repeat( &Screen::forward, 1 );

myScreen.repeat( &Screen::down, 20 );

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

Action::Menu() = {

&Screen::home,

&Screen::forward,

&Screen::back,

&Screen::up,

&Screen::down,

&Screen::end

};

enum CursorMovements {

HOME, FORWARD, BACK, UP, DOWN, END

};

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

Screen& Screen::move( CursorMovements cm )

{

( this-*Menu[ cm ] )();

return *this;

}

У оператора взятия индекса ([]) приоритет выше, чем у оператора указателя на функцию-член (-*). Первая инструкция в move() сначала по индексу выбирает из таблицы Menu нужную функцию-член, которая и вызывается с помощью указателя this и оператора указателя на функцию-член. move() можно применять в интерактивной программе, где пользователь выбирает вид перемещения курсора из отображаемого на экране меню.