Д. Вандевурд, Н.М. Джосаттис - Шаблоны C++. Справочник разработчика (2003) (1160769), страница 80
Текст из файла (страница 80)
Такой вызов может иметь синтаксис р->вй () или подобный, где р— указатель на объект класса, членом которого является функция тй (), или на объект производного класса. Он передается в функцию вй ( ) как скрытый параметр, который в теле функции известен как ГЫ.в. Функция-член жй ( ] может быть определена в подобъекте, на который указывает р, или может наследоваться этим подобъектом. с1авв В1 ( ргйчаге: 1пг Ы; райс: чойс) тй1(); ) чойс) В1::жй1() ( вес)::сонг « "Ы = " « Ь1 « вес)::епс)1; ) Поскольку юй1 ( ) — это функция-член класса В1, то олощается, что она будет вызываться объектом этого класса.
Таким образом, гЫ в — это указатель на объект класса В1. Добавим к коду следующий фрагмент: с1авв В2 ( ргйчаге: 1пг Ь2; риЫ1с: ') чо1с) мй2 (); ) чойс) В2::жй2() ( вас)::соис « "Ь2 = " « Ь2 « вас)::епс)1( ): Аналогично предыдущему случаю, функция-член г'2 ( ) нуждается в скрытом параметре сМ в, указывающем на объект класса В2. А теперь создадим класс, производный от классов В1 и В2. с1авв Р: риЬ11с В1, риЬ11с В2 ( рг1чаге: 1пс с); 22.3. Указатели на функции-члены В силу приведенного выше объявления обьект типа )з может вести себя как объект типа В1 или объект типа В2. Для этого объект класса Р содержит в себе как подобъект класса В1, так и подобъект класса В2.
Почти во всех известных сегодня 32-битовых реализациях организация объекта класса Р будет иметь вид, проиллюстрированный на рис. 22.1. Таким образом, если размер члена 1пс составляет 4 байта, то адрес переменной-члена Ь1 совпадает со значением указателя сЬ1в, адрес переменной-члена Ь2 равен сЬ1н, увеличенному на 4 байта, а адрес переменной-члена с) равен значению сЬ1в, увеличенному на 8 байтов. Заметим, что подобъект В1 расположен по тому же адресу, что и объект )з в целом, в то время как адрес подобъекта В2 отличается от адреса объекта Р. в выв Рис.
22.1. Типичная организация объекта типа В Теперь рассмотрим следующие элементарные вызовы функций-членов: 1пс вайп() ( В оЬЗг оЬ» . вй1 () 1 оЬ3.вх2()г Для вызова оЬ3 . пК2 () в функцию вй2 () нужно передать адрес подобъекта типа В2 в объекте оЬ5. Если предположить, что объект оЬ3 реализован так, как описано выше, то нужный адрес равен адресу объекта оЬ3 плюс 4 байта. Компилятору С++ несложно сгенерировать код для такой корректировки.
Заметим, что для вызова функции вб1 () такая коррекция не нужна, поскольку адрес обьекта оЬЗ совпадает с адресом его подобьекта типа В1. Однако, когда дело касается указателя на функцию-член, компилятор не знает, какую корректировку нужно выполнить. Чтобы понять, что это действительно так, заменим основную функцию вайп () приведенной ниже. нойс) са11 вевйпп(0 оЬЗ, чозй Р::*рвй() ) ( оЬЗ.*рвб(); з.пС ваз.п() ( (з оьб; са11 вевъпп(оЪ3, Ы)::вб1) Глава 22.
Объекты-функции и обратные вызовы 446 са11 вевйип(оЬэ, йР::вй2); ) Эта ситуация станет для компилятора еще запутаннее, если поместить функции са11 вевйип() ивайп() в разные единицы трансляции. Вывод такой: указатель на функцию-член должен передавать не только информацию об адресе этой функции, но и сведения о корректировке значения указателя сЬ1в для данной функции-члена. Если функция-член приводится к другому типу, эта коррекцня может измениться.
В нашем примере это выглядит, как показано ниже. чоЫ Р: г*рвй а() йрг:вй2; // Коррекция: увеличение // на 4 байта // Коррекция стала равна // нулю уойг) В2::*рвй Ь() (чо16 (В2::*)())рвб а Этот пример приведен, чтобы продемонстрировать различия между указателем на функцию-член и указателем на обычную функшпо. Однако данная схема являегся неполной, если дело касается виртуальных функций.
На практике во многих реализациях для хранения указателей на функции-члены используется структура, состоящая из трех величин. 1. Адрес функции-члена или значение ж)т,ь, если функция виртуальная. 2. Необходимая коррекция значения указателя сЬ1в. 3. Индекс виртуальной функции. оЬ~.*рвй(...) рог->*рвй(...) // Вызов функции-члена, на которую // указывает рвй, для объекта оЬЗ // Вызов функции-члена, на которую // указывает рвй, для объекта, на // который указывает рпх Доступ же к обычной функции с помощью указателя, напротив, операция унарная: (*рек) () Подробности этого вопроса выходят за рамки настоящей книги. Читателям, которые интересуются данной темой, рекомендуем обратиться к книге Стэна Липпмана (б(ап Ь~рршап) /лзй1е гйе С++ 0((/есг Мок(е( [2Ц.
Там же можно прочитать, что указатели на данные-члены — это, как правило, совсем не указатели, а величины смещений относительно указателя сМ.в, которые нужны, чтобы получить доступ к данному полю (для их представления обычно достаточно одного машинного слова). Наконец, заметим, что доступ к функции-члену с помощью указателя на нее — это на самом деле бинарная операция.
Для ее выполнения нужна информация не только об указателе, но и об объекте, к которому относится указатель. Именно поэтому в язык программирования пришлось ввести особые операторы разыменования указателей на ланные-члены . * и ->*. 22.4. Функторы-классы 447 Оператор разыменования можно опустить, поскольку он неявно подразумевается в операторе вызова функции. Позтому приведенное выше выражение обычно записывают просто как ртт() Для указателей на функции-члены такая неявная форма отсутствует . 22.4. Функторы-классы Несмотря на наличие в С++ указателей на функции, которые сами по себе являются функторами, все же часто встречаются ситуации, когда предпочтительнее использовать объект класса, в котором перегружен оператор вызова функции. Это позволяет повысить гибкость работы программы, ее производительность нли обе эти характеристики.
22.4.1. Первый пример фуикторов-классов Приведем простой пример функтора-класса // Ецпстогв/бцпстог1.срр ййпс1цс(е <1овсгеащ> // Класс объектов-функций, возвращающих константы с1авв Сопвтапт1псрцпссог ( рг1чатез 1пт ча1це; // Значение, возвращаемое // при "вызове функции" рцЬ11с: // Конструктор: инициализация возвращаемым значением Сопвтапт1птрцпстот(1пт с) : ча1це(с) ( ) // "Вызов функции" 1пт орегатог () () совет [ гетцтп ча1це; ) // Пользовательская функция, использующая объект-функцию чоЫ с11епт (СопвтаптХперцпстот соплей сЫ] ( 4 Также ис выпспяястся неявное сведение имени фувхпии-члена, например мутуре:: Рх-зпщ к указателю ла зту Фуихппючлен. Амперсязщ в выражениях, подобиьж ьиутуре:: рт за г, опускать нельзя, хотя хорошо юмюспс что для обычных функций происходит неявное сведение Л к ат. Глава 22. Объекты-функции и обратные вызовы вес)::соус « "са111пд Ьас)с йипссог уйе1<)в «сН() « '~п~ йпс ма1п () сопвсапс1пгрцпссог вечеп(7)т Сопвсапг1пгрцпсгог гоггусмо(42); с11епс(вечеп)т с11епс (боггусмо) т Здесь Сопвгапг1пгвцпсгог — это класс, с помощью которого можно генерировать функторы.
Таким образом, при создании объекта сопвсапс1псрцпссог вечен(7)т // создание объекта-функции выражение вечеп(); // Вызов орегаког() для объекта-функции представляет собой вызов орегасог() для объекта вечен, а не вызов функции вечен () . Того же эффекта можно достичь (косвенно), передавая в функцию с11епс () объекты-функции вечен и гогсубйо с помощью параметра с11. В этом примере иллюстрируется чуть ли не самое важное преимущество класса функ- торов над указателями на функции: возможность ассоциировать с функцией некоторые состояния (данные). Это фундаментальное улучшение возможностей механизмов обратного вызова.
У нас есть возможность получить в распоряжение несколько "экземпляров" функции, поведение которых (в определенном смысле) параметризовано. 22.4.2. Типы фуикторов-классов:, Следует отметить, что возможности функторов-классов не ограничиваются тем, что с их помощью передается информация о состоянии. Но сути, если в функторе никакое состояние не инкапсулировано, его поведение полностью определяется его типом, а чтобы нужным образом изменить поведение библиотечного компонента, достаточно передать в него параметр типа в виде параметра шаблона. Классическая иллюстрация этого особого случая — классы контейнеров, в которых объекты остаются. отсортированными в определенном порядке.
Критерий сортировки задается с помощью аргумента шаблона, и, поскольку он является частью типа контейнер~ непреднамеренное смешивание контейнеров с разными критериями сортиров«и (например, при присвоении) выявляется системой контроля типов. Контейнеры вес и шар, входящие в стандартную библиотеку С++, параметризованы именно таким образом. Например, если два разных объекта класса вес определены с по мощью одного и того же типа элементов Регвоп, но с разными критериями сортировки то сравнение этих объектов приведет к возникновению ошибки на этапе компиляции.