Делегаты на C++
- « Предыдущая стр.
- Следующая стр. »
Введение
Делегаты - это объектно-ориентированные указатели на функции, используемые для callback-вызовов в среде CLR фирмы Microsoft. Делегат можно связать со статической функцией или с нестатическим методом любого класса (единственное условие - совпадение сигнатуры метода с сигнатурой, указанной в описании делегата). Затем связанную с делегатом функцию или метод можно вызывать, используя стандартный синтаксис вызова функции в C++. Несколько делегатов можно связать в цепочку. Благодаря этому можно "одним махом" вызвать все связанные с ними callback-функции. Следующий пример демонстрирует применение делегатов в языке C#.
>using System;
>using System.IO;
>namespace CSharpDelegates {
> class App {
> // Определяем делегат Callback,
> // который принимает 1 параметр и ничего не возвращает.
> public delegate void Callback(string str);
> // Это метод класса App.
> public void OutputToConsole(string str) {
> Console.WriteLine(str);
> }
> // А это статический метод класса App.
> public static void OutputToFile(string str) {
> StreamWriter sw = new StreamWriter("output.txt", true);
> sw.WriteLine(str);
> sw.Close();
> }
> public static void Main(string[] args) {
> App app = new App();
> // Создаём делегат.
> App.Callback callback = null;
> if (callback != null) callback("1");
> // Добавляем ссылку на OutputToFile.
> // Вызываем её через делегата.
> callback += new App.Callback(App.OutputToFile);
> if (callback != null) callback("2");
> // Добавляем ссылку на OutputToConsole.
> // Вызывается вся цепочка:
> // сначала OutputToFile, потом OutputToConsole.
> callback += new App.Callback(app.OutputToConsole);
> if (callback != null) callback("3");
> // Убираем ссылку на OutputToFile.
> // Вызывается только OutputToConsole.
> callback -= new App.Callback(App.OutputToFile);
> if (callback!= null) callback("4");
> // Убираем оставшуюся ссылку на OutputToConsole.
> callback -= new App.Callback(app.OutputToConsole);
> if (callback != null) callback("5");
> }
> }
>}
Делегаты в CLR удобны, типобезопасны и эффективны. Последнее время на форумах RSDN часто поднимается вопрос о том, можно ли реализовать делегаты с аналогичными свойствами, оставаясь в рамках "чистого" C++. Оказывается, это вполне возможно. В этой статье я покажу, как это сделать.
Частное решение
Для начала создадим делегат для callback-вызова функций и методов с простейшей сигнатурой void(void). Интерфейс этого делегата будет выглядеть так.
>class IDelegateVoid {
>public:
> virtual ~IDelegateVoid() {}
> virtual void Invoke() = 0;
> virtual bool Compare(IDelegateVoid* pDelegate) = 0;
>};
Invoke используется для вызова функции или метода, связанного с делегатом, а Compare сравнивает 2 делегата и возвращает true, если они связаны с одной и той же функцией (методом). Очевидно, что реализация интерфейса IDelegateVoid будет отличаться для статических функций и нестатических методов класса, поэтому мы разнесём эти реализации по различным классам. Класс CStaticDelegateVoid будет "отвечать" за статические функции, а класс CMethodDelegateVoid - за нестатические методы.
Класс CStaticDelegateVoid просто инкапсулирует указатель типа void (*)():
>class CStaticDelegateVoid: public IDelegateVoid {
>public:
> typedef void (*PFunc)();
> CStaticDelegateVoid(PFunc pFunc) { m_pFunc = pFunc; }
> virtual void Invoke() { m_pFunc(); }
> virtual bool Compare(IDelegateVoid* pDelegate);
>private:
> PFunc m_pFunc;
>};
Метод Compare должен проверить, что переданный ему указатель IDelegateVoid* в действительности ссылается на объект CStaticDelegateVoid. Если это не так, делегаты различны (ссылаются на разные функции) и Compare просто возвращает false. Иначе результат определяется сравнением переменных-членов m_pFunc у двух объектов. Реализация этой идеи выглядит так.
>bool CStaticDelegateVoid::Compare(IDelegateVoid *pDelegate) {
> CStaticDelegateVoid* pStaticDel = dynamic_cast‹CStaticDelegateVoid*›(pDelegate);
> if (pStaticDel == NULL || pStaticDel-›m_pFunc != m_pFunc) return false;
> return true;
>}
Класс CMethodDelegateVoid чуть-чуть сложнее. Он должен инкапсулировать указатель на объект и указатель на метод этого объекта. Поскольку в C++ указатели на методы двух разных классов принципиально отличаются (и могут даже иметь разный размер), нам нужна отдельная реализация