Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
action(myScreen, get); // использует предварительно определенную
// переменную get
action(myScreen, &Screen::get); // передает адрес явно
Псевдонимы типа облегчают чтение и написание кода, использующего указатели.
Таблицы указателей на функцию-членКак правило, перед использованием указатели на функции и указатели на функции-члены хранят в таблице функций (см. раздел 14.8.3). Когда у класса есть несколько членов того же типа, такая таблица применяется для выбора одного из набора этих членов. Предположим, что класс Screen дополнен несколькими функциями-членами, каждая из которых перемещает курсор в определенном направлении:
class Screen {
public:
// другие члены интерфейса и реализации, как прежде
Screen& home(); // функции перемещения курсора
Screen& forward();
Screen& back();
Screen& up();
Screen& down();
};
Каждая из этих новых функций не получает никаких параметров и возвращает ссылку на вызвавший ее объект класса Screen.
Можно определить функцию move(), способную вызвать любую из этих функций и выполнить указанное действие. Для поддержки этой новой функции в класс Screen добавлен статический член, являющийся массивом указателей на функции перемещения курсора:
class Screen {
public:
// другие члены интерфейса и реализации, как прежде
// Action - указатель, который может быть присвоен любой из
// функций-членов перемещения курсора
using Action = Screen&(Screen::*)();
// задать направление перемещения;
// перечисления описаны в разделе 19.3
enum Directions { HOME, FORWARD, BACK, UP, DOWN };
Screen& move(Directions);
private:
static Action Menu[]; // таблица функций
};
Массив Menu содержит указатели на каждую из функций перемещения курсора. Эти функции будут храниться со смещениями, соответствующими перечислителям перечисления Directions. Функция move() получает перечислитель и вызывает соответствующую функцию:
Screen& Screen::move(Directions cm) {
// запустить элемент по индексу cm для объекта this
return (this->*Menu[cm])(); // Menu[cm] указывает на функцию-член
}
Вызов move() обрабатывается следующим образом: выбирается элемент массива Menu по индексу cm. Этот элемент является указателем на функцию-член класса Screen. Происходит вызов функции-члена, на которую указывает этот элемент от имени объекта, на который указывает указатель this.
Когда происходит вызов функции move(), ему передается перечислитель, указывающий направление перемещения курсора:
Screen myScreen;
myScreen.move(Screen::HOME); // вызывает myScreen.home
myScreen.move(Screen::DOWN); // вызывает myScreen.down
Остается только определить и инициализировать саму таблицу:
Screen::Action Screen::Menu[] = { &Screen::home,
&Screen::forward,
&Screen::back,
&Screen::up,
&Screen::down,
};
Упражнения раздела 19.4.2Упражнение 19.14. Корректен ли следующий код? Если да, то что он делает? Если нет, то почему?
auto pmf = &Screen::get_cursor; pmf = &Screen::get;
Упражнение 19.15. В чем разница между обычным указателем на функцию и указателем на функцию-член?
Упражнение 19.16. Напишите псевдоним типа, являющийся синонимом для указателя, способного указать на переменную-член avgprice класса Sales_data.
Упражнение 19.17. Определите псевдоним типа для каждого отдельного типа функции-члена класса Screen.
19.4.3. Использование функций-членов как вызываемых объектов
Как уже упоминалось, для вызова через указатель на функцию-член, нужно использовать операторы .* и ->* для связи указателя с определенным объектом. В результате, в отличие от обычных указателей на функцию, указатель на функцию-член класса не является вызываемым объектом; эти указатели не поддерживают оператор вызова функции (см. раздел 10.3.2).
Поскольку указатель на член класса не является вызываемым объектом, нельзя непосредственно передать указатель на функцию-член алгоритму. Например, если необходимо найти первую пустую строку в векторе строк, вполне очевидный вызов не сработает:
auto fp = &string::empty; // fp указывает на функцию empty()
// класса string
// ошибка: для вызова через указатель на член класса следует
// использовать оператор .* или ->*
find_if(svec.begin(), svec.end(), fp);
Алгоритм find_if() ожидает вызываемый объект, но предоставляется указатель на функцию-член fp. Этот вызов не будет откомпилирован, поскольку код в алгоритме find_if() выполняет примерно такой оператор:
// проверяет применимость данного предиката к текущему элементу,
// возвращает true
if (fp(*it)) // ошибка: для вызова через указатель на член класса
// следует использовать оператор ->*
Использование шаблона function для создания вызываемого объектаОдин из способов получения вызываемого объекта из указателя на функцию-член подразумевает использование библиотечного шаблона function (см. раздел 14.8.3):
function<bool (const string&)> fcn = &string::empty;
find_if(svec.begin(), svec.end(), fcn);
Здесь шаблону function указано, что empty() — это функция, которая может быть вызвана со строкой и возвращает значение типа bool. Обычно объект, для которого выполняется функция-член, передается неявному параметру this. Когда шаблон function используется при создании вызываемого объекта для функции-члена, следует преобразовать код так, чтобы сделать этот неявный параметр явным.
Когда объект шаблона function содержит указатель на функцию-член, класс function знает, что для вызова следует использовать соответствующий оператор указателя на член класса. Таким образом, можно предположить, что у функции find_if() будет код наподобие следующего:
// если it является итератором в функции find_if(), то *it - объект
// в заданном диапазоне
if (fcn(*it)) // fcn - имя вызываемого объекта в функции find_if()
Его и выполнит шаблон класса function, используя соответствующий оператор указателя на член класса. Класс function преобразует этот вызов в такой код:
// если it является итератором в функции find_if(), то *it - объект
// в заданном диапазоне
if (((*it).*p)()) // p - указатель на функцию-член в функции fcn
При определении объекта шаблона function следует указать тип функции, сигнатура которой определяет представляемые вызываемые объекты. Когда вызываемой объект является функцией-членом, первый параметр сигнатуры должен представить (обычно неявный) объект, для которого будет выполнена функция-член. Передаваемая шаблону function сигнатура должна определять, будет ли объект передан как указатель или как ссылка.
При определении вызываемого объекта fcn() было известно, что нужно вызвать функцию find_if() для последовательности строковых объектов. Следовательно, от шаблона function требовалось создать вызываемый объект, получающий объекты класса string. Если бы вектор содержал указатели на тип string, от шаблона function требовалось бы ожидать указатель:
vector<string*> pvec;
function<bool (const string*)> fp = &string::empty;
// fp получает указатель на string и использует оператор ->* для вызова
// функции empty()
find_if(pvec.begin(), pvec.end(), fp);
Использование шаблона mem_fn для создания вызываемого объектаЧтобы использовать шаблон function, следует предоставить сигнатуру вызова члена, который предстоит вызвать. Но можно позволить компилятору вывести тип функции-члена при использовании другого библиотечного средства, шаблона mem_fn, определенного, как и шаблон function, в заголовке functional. Как и шаблон function, шаблон mem_fn создает вызываемый объект из указателя на член класса. В отличие от шаблона function, шаблон mem_fn выведет тип вызываемого объекта из типа указателя на член класса: