Имитация делегатов C# в стандартном C++
ОГЛАВЛЕНИЕ
Введение
Язык C# имеет возможность, называемую делегаты, которые позволяют легко отделить инициатора события от конечного обработчика. По существу, они выполняют такую же роль, что и указатели функций в C и указатели на функции-члены в C++, но они намного более гибкие. В частности, они могут использоваться для указания на любую функцию любого объекта, если она имеет соответствующую сигнатуру.
Эта статья описывает наш подход к обеспечению такой же функциональности путем использования только стандартного C++. Есть много достойных альтернатив, которые можно легко найти через поиск в Google по ключевым словам "C++ делегаты". Нашей задачей было создание синтаксиса, аналогичного используемому в управляемом C++ и C#.
Предпосылки
Если вы уже знаете о делегатах все, что вам нужно, пропустите этот раздел.
Делегаты – это не новая идея. Продукты Borland Delphi и C++ Builder использовали их с самого начала, чтобы сопровождать Библиотеку визуальных компонентов, хотя они назывались ‘указатели методов’ в Delphi и ‘замыкания’ в Builder (это одинаковые вещи). По существу, замыкание – это указатель объектно-ориентированной функции. Внутри он просто хранит адрес вызываемой функции и адрес объекта, для которого она вызывается (то есть скрытый параметр 'this', передаваемый функции).
Важно следующее: сомнения относительно того, какой другой класс будет обрабатывать события, которые класс порождает, являются основной причиной создания визуальной среды разработки Delphi, основанной на использовании компонентов. Уменьшение связанности между классами – это хорошее дело.
Сейчас делегаты в стиле C# предоставляют такой же сервис и для языков .NET, но в стандартном C++ их нет. Указатели на члены-функции очень ограничены по сравнению с делегатами, хотя они использовались совместно с макроопределениями в таких библиотеках, как Borland OWL в прошлом, и с Trolltech's Qt сегодня.
С помощью делегатов .NET вы можете даже присоединить несколько обработчиков к одному событию. Они все будут вызваны (последовательно) при вызове делегата. Это значит, что вы можете безопасно присоединить ваш разработчик к событию без разрушения какой-либо другой связи. Мы еще не использовали эту возможность, но осознаем ее потенциал. Однако мы не знаем, может ли эта возможность быть эффективно реализована. Вызов замыканий Borland с одиночным приведением сокращает пару кодов операции, которые помещают 'this' в стек и вызывают функцию, поэтому они эффективны и малозатратны в использовании. [Жаль, что они не входят в стандарт.] Но как только вы начинаете поддерживать динамическую коллекцию целей, все становится намного сложнее. В идеале .NET должен иметь в наличии очень эффективные эффективные делегаты с одиночным приведением, а также реализовывать делегаты с многократным приведением в их – это лучшее, что можно было бы иметь под рукой.
NET различает 'делегаты' и 'события'. Делегат – это улучшенный указатель функции; делегат используется для событий – член класса, которому вы хотите присвоить адреса обработчиков, которые будут вызваны, когда класс вызовет делегат. Мы считаем это разграничение бесполезным – как еще могут использоваться делегаты? Мы иногда используем термины по очереди.
C# и управляемый C++ имеют довольно аккуратный синтаксис для присваивания обработчиков делегату/событию:
mnuPopup->Popup += new System::EventHandler(this, mnuPopup_Popup);
Когда контекстное меню mnuPopup отображается пользователем, оно вызывает его событие/делегат всплывающего меню. Затем будет вызван обработчик, реализованный в форме mnuPopup_Popup.