Вызов функции через указатель выполняется так: (*имя-указателя)(аргументы);
Тот же вызов можно написать в более простой форме: имя-указателя(аргументы);
Рассмотрим несколько простых примеров. Допустим, у нас объявлен указатель на функцию:
int (*pf)(void);
Этому указателю можно присвоить адрес любой функции с таким же прототипом, в том числе и библиотечной. Например, такой прототип имеет функция-генератор случайного числа rand (), который объявлен в заголовке библиотеки <cstdlib>:
int fl(void) { return 1; }
// вызов fl() // вызов f2() // вызов rand()
int f2(void) { return 2; }
pf = fl; cout << pf() << endl;
pf = f2; cout << pf() << endl;
pf = &rand; cout << pf() << endl;
Напишем теперь простой класс
class Constant { int value; public:
Constant(const int &a): value(a) {} int get(void) { return value; }
};
Метод get () с виду имеет тот же прототип, что и функции в приведенном ранее фрагменте. Однако попытки присвоить адрес метода get() указателю pf компилятор пресекает «на корню». Да и не совсем понятно, как этот адрес задавать. Вариантов два.
1. Объявить объект и взять адрес метода в объекте:
Constant А(5); pf = &A.get();
2. Не объявлять объект, а приписать префикс класса:
pf = &Constant::get;
Первый вариант вообще неверный: и Visual C++.NET 2003, и Borland С++ Builder 6 сообщают, что операцию взятия адреса & так использовать нельзя. Второй вариант ближе к истине: оба компилятора сообщают только о невозможности выполнить преобразование типов. Попытки прописать преобразование явно не проходят:
pf = static_cast<int (*)(void)>(&Constant::get);
pf = reinterpret_cast<int (*)(void)>(&Constant::get);
pf = (int (*)(void))(&Constant::get);
Все эти варианты вызывают ту же ошибку компиляции — невозможность преобразования типов. Таким образом, тип указателя на метод класса кардинально отличается от типа указателя на функцию: адрес метода нельзя присвоить указателю на функцию, даже если внешне их прототипы совпадают. Это становится понятным, если мы вспомним, что нестатические методы получают дополнительный параметр — указатель thi s.
Аналогично, нельзя присвоить обычному указателю на функцию адрес виртуального метода — в этом случае дело усугубляется еще наличием в составе объекта указателя на таблицу виртуальных методов. А вот to статическими методами картина другая! Статический метод не получает никаких «лишних параметров», поэтому его адрес можно присваивать обычному указателю на функцию без всяких преобразований. Добавим в класс Constant статическое поле и статический метод с нужным нам прототипом:
class Constant { int value;
static int d; public:
Constant(const int &a): value(a) {} int get(void) { return value; }
static int getd(void) { return d; } // статический метод
};
Тогда нашему указателю pf можно присвоить адрес статического метода согласно второму из приведенных ранее вариантов, например:
pf = Constant:rgetd; // или pf = &Constant::getd;
Префикс, естественно, необходимо писать.
Указатель на метод объявляется по-другому (см. п. п. 8.3.3 в [1]) — нужно задать имя класса в качестве префикса:
int (Constant::*pm)(void);
Такому указателю можно присваивать адреса обычных и виртуальных методов согласно второму из приведенных ранее вариантов, например:
pm = &Constant::get; // или рт = Constant::get;
Адрес статического метода, так же как и адрес обычной функции, такому указателю присвоить нельзя — возникает ошибка трансляции: компилятор сообщает о невозможности выполнить преобразование типов.
И косвенный вызов метода выполняется по-другому — с помощью операции выбора члена класса . * или ->* (см. п. 5.5 в [1]). Несмотря на то что указатель на член класса является отдельной независимой от класса переменной, вызов метода по указателю возможен только при наличии объекта, например:
Constant А(5);
cout << (A.*pm)() << endl;
Выражение (A. *pm) () означает следующее: для объекта А вызвать метод, чей адрес записан в указателе рт. Слева от операции . * — объект, справа — указатель на метод. Скобки вокруг выражения А. *рт писать обязательно, так как приоритет операции вызова функции (()) выше, чем приоритет операции выбора члена класса (. *).
В выражении (объект. *указатель) можно заменить часть объект, записью указатель->, например:
Constant *рс = new Constant(7); cout << (pc->*pm)() << endl;
Обратите внимание: в выражении (pc->*pm) слева — обычный указатель на динамический объект, а справа — указатель на метод этого объекта. Это два совершенно разных типа указателя.
Интересно, что в Visual C++.NET 2003 размер sizeof (pf) указателя на функцию совпадает с размером si zeof (pm) указателя на член класса и равен 4 байтам. А вот система Borland С++ Builder 6 выдает совершенно разные цифры: размер указателя на функцию равен 4 байтам, а размер указателя на метод — 12 байт! |