Делегаты на 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++ указатели на методы двух разных классов принципиально отличаются (и могут даже иметь разный размер), нам нужна отдельная реализация