Выбрать главу

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';