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

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() можно применять в интерактивной программе, где пользователь выбирает вид перемещения курсора из отображаемого на экране меню.

Поделитесь на страничке

Следующая глава >

Похожие главы из других книг

Имя класса

Из книги Самоучитель UML автора Леоненков Александр

Имя класса Имя класса должно быть уникальным в пределах пакета, который описывается некоторой совокупностью диаграмм классов (возможно, одной диаграммой). Оно указывается в первой верхней секции прямоугольника. В дополнение к общему правилу наименования элементов


5.4.2 Уточнение* Имени Члена

Из книги C++ автора Хилл Мюррей

5.4.2 Уточнение* Имени Члена – * Иногда называется также квалификацией. (прим. перев.)Иногда полезно делать явное различие между именами члнов класса и прочими именами. Для этого используется операция ::, «разрешения области видимости»:class x (* int m; public: int readm() (* return x::m; *) void setm(int


5.4.2 Уточнение* Имени Члена

Из книги Язык программирования С# 2005 и платформа .NET 2.0. [3-е издание] автора Троелсен Эндрю

5.4.2 Уточнение* Имени Члена – * Иногда называется также квалификацией. (прим. перев.)Иногда полезно делать явное различие между именами члнов класса и прочими именами. Для этого используется операция ::, «разрешения области видимости»:class x (* int m; public: int readm() (* return x::m; *) void setm(int


Тип класса

Из книги Delphi. Учимся на примерах автора Парижский Сергей Михайлович

Тип класса Любой язык, совместимый с .NET, поддерживает, как минимум, тип класса, который является "краеугольным камнем" объектно-ориентированного программирования (ООП). Класс может состоять из любого числа членов (таких, как свойства, методы и события) и элементов данных


Тип класса в C#

Из книги Microsoft Visual C++ и MFC. Программирование для Windows 95 и Windows NT автора Фролов Александр Вячеславович

Тип класса в C# Если вы имеете опыт создания объектов в рамках какого-то другого языка программирования, то, несомненно, знаете о роли определений классов. Формально говоря, класс – это определенный пользователем тип (User-Defined Type - UDT), который скомпонован из полей данных


Методы класса

Из книги Инфобизнес за один день автора Ушанов Азамат

Методы класса Метод — это процедура или функция, реализованная в классе. В качестве примера пользовательских методов можно привести процедуры обработки событий. Кроме того, методом становится любая процедура или функция, заголовок которой указан в объявлении


Конструкторы класса

Из книги C++ для начинающих автора Липпман Стенли

Конструкторы класса Для класса CObject определены два конструктора. Первый конструктор используется по умолчанию и не имеет параметров. Именно этот конструктор вызывается конструкторами классов, наследованных от CObject:CObject();Второй конструктор класса CObject называется


Конструктор класса

Из книги автора

Конструктор класса Класс CString имеет несколько различных конструкторов, позволяющих создавать строки на основе различных данных.Конструктор класса CString, используемый по умолчанию, не имеет параметров. Он создает пустую строку. В последствии вы можете записать в нее любой


8.5. Гарантия единственности копии переменной-члена

Из книги автора

8.5. Гарантия единственности копии переменной-члена ПроблемаИмеется переменная-член, у которой должен быть только один экземпляр независимо от числа создаваемых экземпляров класса. Этот тип переменных-членов обычно называется статическими членами или переменными


2. Профессиональная деятельность члена вашей семьи

Из книги автора

2. Профессиональная деятельность члена вашей семьи Если вы не работаете на данный момент или вы – молодой человек без опыта работы, для вас следующий пункт. Возможно, кто-то из вашей семьи что-то очень хорошо делает. Например, ваш брат или сестра – фотограф, финансовый


8.5.4. Определение члена пространства имен

Из книги автора

8.5.4. Определение члена пространства имен Мы видели, что определение члена пространства имен может появиться внутри определения самого пространства. Например, класс matrix и константа pi появляются внутри вложенного пространства имен MatrixLib, а определения функций operator+() и


14.1. Инициализация класса

Из книги автора

14.1. Инициализация класса Рассмотрим следующее определение класса:class Data {public:int ival;char *ptr;};Чтобы безопасно пользоваться объектом класса, необходимо правильно инициализировать его члены. Однако смысл этого действия для разных классов различен. Например, может ли ival


14.2. Конструктор класса

Из книги автора

14.2. Конструктор класса Среди других функций-членов конструктор выделяется тем, что его имя совпадает с именем класса. Для объявления конструктора по умолчанию мы пишем2:class Account {public:// конструктор по умолчанию ...Account();// ...private:char *_name;unsigned int _acct_nmbr;double _balance;};Единственное


14.3. Деструктор класса

Из книги автора

14.3. Деструктор класса Одна из целей, стоящих перед конструктором, - обеспечить автоматическое выделение ресурса. Мы уже видели в примере с классом Account конструктор, где с помощью оператора new выделяется память для массива символов и присваивается уникальный номер счету.


14.6.1. Инициализация члена, являющегося объектом класса

Из книги автора

14.6.1. Инициализация члена, являющегося объектом класса Что произойдет, если в объявлении _name заменить C-строку на тип класса string? Как это повлияет на почленную инициализацию по умолчанию? Как надо будет изменить явный копирующий конструктор? Мы ответим на эти вопросы в