• Программирование
  • C++
  • Визуальное моделирование сложных реагирующих систем при помощи диаграмм состояния UML Harel

Указатели функций-членов и наиболее быстрые делегаты C++ - Использование указателей функций-членов

ОГЛАВЛЕНИЕ

 

Использование указателей функций-членов

Вероятно, мы убедили вас, что указатели функций-членов – это нечто странное. Но для чего они могут быть полезны? В результате изучения опубликованного в сети кода мы выявили две основных сферы их использования:

  1. надуманные примеры, демонстрирующие синтаксис начинающим программистам в C++, и
  2. реализации делегатов!

Они также имеют очевидные способы применения в коротких адаптерах функций в библиотеках STL и Boost, позволяющих вам применять функции-члены в стандартных алгоритмах. В таких случаях они используются во время компиляции; обычно в скомпилированном коде на самом деле нет указателей функций. Самое интересное применение указателей функций-членов – это определение составных интерфейсов. Таким способом можно делать впечатляющие вещи, но мы не нашли много примеров. Большей частью эти задачи можно выполнить более элегантно с помощью виртуальных функций или путем перестройки задачи. Чаще всего указатели функций-членов применяются в различных видах сред разработки приложений. Они формируют ядро системы обмена сообщениями MFC.

Когда вы используете макрос карты сообщений MFC (например, ON_COMMAND), вы фактически заполняете массив, содержащий идентификаторы сообщений и указатели функций-членов (в особенности, указатели функций-членов CCmdTarget::*). По этой причине классы MFC должны порождаться от CCmdTarget, если они должны обрабатывать сообщения. Но различные функции обработки сообщений имеют различные списки параметров (например, обработчики OnDraw имеют CDC * в качестве первого параметра), поэтому массив должен содержать указатели функций-членов различных типов. Как MFC справляется с этим? Они применяют хитрый прием, помещая все возможные указатели функций-членов в огромное объединение, чтобы разрушить стандартный контроль типов в C++. (Смотрите более подробную информацию в объединении MessageMapFunctions в afximpl.h и cmdtarg.cpp.) Поскольку MFC – это важная часть кода, на практике все компиляторы C++ поддерживают этот прием.

Мы не смогли найти много примеров хорошего использования указателей функций-членов, отличных от использования во время компиляции. При всей своей сложности они добавляют мало существенного в язык. Трудно не прийти к выводу, что конструкция указателей функций-членов C++ недоработана.

Эта статья содержит одну важную мысль: Нелепо то, что стандарты C++ позволяют вам выполнять преобразования между указателями функций-членов, но не позволяют вызывать их, когда вы выполнили преобразование. Это нелепо по трем причинам. Во-первых, преобразование не всегда работает во многих популярных компиляторах (преобразование типов соответствует стандартам, но оно не переносимо). Во-вторых, во всех компиляторах, если преобразование успешно, вызов преобразованного указателя функции-члена ведет себя точно так как вы предполагали: нет необходимости классифицировать его как "неопределенное поведение". (Вызов переносим, но не стандартен!) В-третьих, разрешение преобразования типов без разрешения вызова полностью бесполезна; но если возможны преобразование и вызов, то легко реализовать эффективные делегаты с огромной выгодой для языка.

Чтобы удостовериться в этом спорном утверждении, рассмотрим файл, состоящий только из следующего кода. Это допустимый C++.

class SomeClass;
typedef void (SomeClass::* SomeClassFunction)(void);
void Invoke(SomeClass *pClass, SomeClassFunction funcptr) {
  (pClass->*funcptr)(); };

Учтите, что компилятор должен сгенерировать ассемблерный код, чтобы вызвать указатель функции-члена, не зная ничего о классе SomeClass. Если компоновщик не выполнит изощренную оптимизацию, то этот код должен работать правильно независимо от реального определения класса. Из этого следует, что вы можете безопасно вызывать указатель функции-члена, который был преобразован из полностью отличного класса.

Чтобы пояснить вторую часть утверждения, что преобразование типов не работает в соответствии со стандартом, нужно подробно обсудить, как именно компиляторы реализуют указатели функций-членов. Это также поможет объяснить, почему правила использования указателей функций-членов настолько ограничены. Сложно получить точную информацию об указателях функций-членов, часто попадается ложная информация, поэтому мы проверили ассемблерный код, порождаемый широким диапазоном компиляторов.