В ситуации, которая показана в примере 15.1, применение указателя на функцию является хорошим решением, если
UpdateProgress
и
longOperation
ничего не должны знать друг о друге. Например, функцию, которая обновляет индикатор
состояния процесса в диалоговом окне пользовательского интерфейса (user interface — UI), в окне консольного режима или где-то еще, не заботит контекст, в котором она вызывается. Аналогично функция
longOperation
может быть частью некоторого программного интерфейса загрузки данных, которого не заботит место вызова: из графического UI, из окна консольного режима или из фонового процесса.
Сначала потребуется определить сигнатуру функции, которую вы планируете вызывать, и создать для нее
typedef
. Оператор
typedef
— ваш помощник в тех случаях, когда приходится иметь дело с указателями функций, потому что они имеют не очень привлекательный синтаксис. Рассмотрим, как обычно объявляется такой указатель на примере переменной
f
, которая содержит адрес функции, принимающей единственный аргумент целого типа и возвращающей значения типа
boolean
. Это может выглядеть следующим образом
bool (*f)(int); // f - имя переменной
Вы можете справедливо возразить, что здесь нет ничего особенного и я просто излишне драматизирую ситуацию. Но что вы скажете, если требуется определить вектор
vector
таких указателей?
vector<bool (*)(int)> vf;
Или их массив?
bool (*af[10])(int);
Форма представления указателей на функции отличается от обычных переменных С++, которые обычно задаются в виде (квалифицированного) имени типа, за которым идет имя переменной. Поэтому они вносят путаницу при чтении программного кода.
Итак, в примере 15.1 я использовал следующий
typedef
.
typedef bool (*FuncPtrBoolInt)(int);
Сделав это, я могу свободно объявлять указатели функций с сигнатурой, возвращающей значение
bool
и принимающей единственный аргумент, как это я бы делал для параметра любого другого типа, например.
void longOperation(FuncPtrBoolInt f) { // ...
Теперь все, что надо сделать в
longOperation
, — это вызвать
f
, как если бы это была любая обычная функция.
f(l/1000000);
Таким образам, здесь
f
может быть любой функцией, которая принимает аргумент целого типа и возвращает
bool
. Предположим, что в вызывающей функции
longOperation
не требуется обеспечивать продвижение индикатора состояния процесса. Тогда ей можно передать указатель на функцию без операций.
bool whoCares(int i) {return(true);}
//...
longOperation(whoCares);
Более важно то, что выбор функции, передаваемой
longOperation
, может осуществляться динамически на этапе выполнения.
15.2. Применение указателей для членов класса
Проблема
Требуется обеспечить адресную ссылку на данное-член или на функцию-член.
Решение
Используйте имя класса и оператор области видимости (
::
) со звездочкой для правильного квалифицирования имени. Пример 15.2 показывает, как это можно сделать.
Пример 15.2.
Получение указателя на член класса
#include <iostream>
#include <string>
class MyClass {
public:
MyClass : ival_(0), sval_("foo") {}
~MyClass {}
void incr {++ival_;}
void decr {ival_--;}
private:
std::string sval_;
int ival_;
};
int main {
MyClass obj;
int MyClass::* mpi = &MyClass::ival_; // Указатели на
void (MyClass::*mpf); // Указатель на функцию-член, у которой
// нет параметров и которая возвращает void
void (*pf); // Обычный указатель на функцию
int* pi = &obj.ival_; // int-указатель, ссылающийся на переменную-член
// типа int, - все нормально.
mpf = &MyClass::incr; // Указатель на функцию-член. Вы не можете
// записать это значение в поток. Посмотрите в
// отладчике, как это значение выглядит.
pf = &MyClass::incr; // Ошибка: &MyClass::inc не является экземпляром
// функции
std::cout << "mpi = " << mpi << '\n';
std::cout << "mps = " << mps << '\n';
std::cout << "pi = " << pi << '\n';
std::cout << "*pi = " << *pi << '\n';
obj.*mpi = 5;
obj.*mps = "bar";
(obj.*mpf); // теперь obj.ival_ равно 6
std::cout << "obj.ival_ = " << obj.ival_ << '\n';
std::cout << "obj.sval_ = " << obj.sval_ << '\n';
}
Обсуждение
Указатели на члены класса выглядят и работают иначе, чем обычные указатели. Прежде всего, они имеют «смешной» синтаксис (не вызывающий смех, но странный). Рассмотрим следующую строку из примера 15.2.
int MyClass::* mpi = &MyClass::ival_;
Здесь объявляется указатель и ему присваивается значение целого типа, которым оказывается член класса
MyClass
. Две вещи отнимают это объявление от обычного
int*
. Во-первых, вам приходится вставлять имя класса и оператор области видимости между типом данного и звездочкой. Во-вторых, при выполнении операции присваивания этому указателю на самом деле не назначается какой то определенный адрес памяти. Значение
&MyClass::ival_
не является каким-то конкретным значением, содержащимся в памяти; оно ссылается на имя класса, а не на имя объекта, но тогда что же это такое на самом деле? Можно представить это значение как смешение данного-члена относительно начального адреса объекта.