15.1. Применение указателей функций для их обратного вызова
Планируется использование некоторой функции func1
, которая на этапе выполнения должна вызывать другую функцию func2
. Однако по той или иной причине нельзя внутри функции func1
жестко закодировать имя функции func2
. Возможно, имя функции func2
неизвестно на этапе компиляции, или func1
относится к программному интерфейсу независимого разработчика, и она не может быть изменена и перекомпилирована В любом случае вам придется воспользоваться функцией обратного вызова (callback function).
При использовании указанных выше функций объявите func1
с указателем на функцию в качестве своего аргумента и передайте ей адрес func2
на этапе выполнения. Используйте typedef
, чтобы программа легче читалась и отлаживалась. Пример 15.1 показывает, как можно реализовать функцию обратного вызова, используя указатель на функцию.
Пример 15.1. Функция обратного вызова
#include <iostream>
// Пример функции обратного вызова
bool updateProgress(int pct) {
std::cout << pct << "% complete...\n";
return(true);
}
// Этот typedef делает программный код более понятным
typedef bool (*FuncPtrBoolInt)(int);
// Функция, которая выполняется достаточно длительное время
void longOperation(FuncPtrBoolInt f) {
for (long l=0; l < 100000000; l++)
if (l % 10000000 == 0)
f(l/1000000);
}
int main() {
longOperation(updateProgress); // нормально
}
В ситуации, которая показана в примере 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_; // Указатели на
std::string MyClass::* mps = &MyClass::sval_; // данные-члены
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';