|
||||
|
Общая реализация Теперь посмотрим, как можно обобщить класс CDelegateVoid для применения с различными сигнатурами. Используя шаблоны, мы можем параметризовать как тип возвращаемого значения, так и типы параметров функций, на которые ссылаются делегаты. В то же время, мы не можем определить единый шаблон, поддерживающий разное количество параметров, поэтому для каждого количества параметров необходимо реализовать свой класс. Поскольку наборы от 0 до 10 параметров покрывают 99% практических нужд при работе с делегатами, нам нужно написать 11 шаблонов делегатов CDelegate0, CDelegate1,…, CDelegate10. Вот как будет начинаться описание делегата, который возвращает произвольное значение и принимает произвольный (но ровно 1) параметр. template‹class TRet, class TP1› class IDelegate1 { public: virtual ~IDelegate1() {} virtual TRet Invoke(TP1) = 0; virtual bool Compare(IDelegate1‹TRet, TP1›* pDelegate) = 0; }; template‹class TRet, class TP1› class CStaticDelegate1: public IDelegate1‹TRet, TP1› { public: typedef TRet (*PFunc)(TP1); CStaticDelegate1(PFunc pFunc) { m_pFunc = pFunc; } virtual TRet Invoke(TP1 p1) { return m_pFunc(p1); } virtual bool Compare(IDelegate1‹TRet, TP1›* pDelegate) { CStaticDelegate1‹TRet, TP1›* pStaticDel = dynamic_cast‹CStaticDelegate1‹TRet, TP1›* ›(pDelegate); if (pStaticDel == NULL || pStaticDel-›m_pFunc != m_pFunc) return false; return true; } private: PFunc m_pFunc; }; Как видим, мы вынуждены постоянно "таскать" за собой список параметров шаблона ‹TRet, TP1›. Для делегата с 10-ю параметрами эти списки полностью загромоздят код. Кроме того, вручную дублировать практически идентичные классы 11 раз - не самая удачная идея. Чтобы избежать лишнего дублирования кода, прибегнем к самому сильнодействующему (и самому опасному) средству языка C++ - препроцессору. Идея состоит в том, чтобы обозначить различающиеся участки кода в реализации классов CDelegateX макросами. Эти различающиеся участки можно разделить на 4 типа: • Параметры шаблонов (например, ‹…, class TP1, class TP2, class TP3›). Список параметров шаблона обозначим макросом TEMPLATE_PARAMS. • Аргументы шаблонов (например, ‹…, TP1, TP2, TP3›). Список аргументов шаблона обозначим макросом TEMPLATE_ARGS. • Параметры функции Invoke (например, (TP1 p1, TP2 p2, TP3 p3)). Список этих параметров обозначим макросом PARAMS. • Аргументы функции Invoke (например, (p1, p2, p3)). Список этих аргументов обозначим макросом ARGS. Кроме этих макросов, нам потребуется макрос SUFFIX, который принимает значения от 0 до 10 и дописывается к именам классов следующим образом: #define COMBINE(a,b) COMBINE1(a,b) #define COMBINE1(a,b) a##b #define I_DELEGATE COMBINE(IDelegate, SUFFIX) #define C_STATIC_DELEGATE COMBINE(CStaticDelegate, SUFFIX) #define C_METHOD_DELEGATE COMBINE(CMethodDelegate, SUFFIX) #define C_DELEGATE COMBINE(CDelegate, SUFFIX) ПРИМЕЧАНИЕ Обратите внимание на использование вспомогательного макроса COMBINE1. Если напрямую реализовать макрос COMBINE как #define COMBINE(a,b) a##b, то результатом подстановки COMBINE(IDelegate, SUFFIX) будет "IDelegateSUFFIX". А это совсем не то, что мы хотим получить. Поэтому использование COMBINE1 в данном случае необходимо. Окончательная версия делегата, обобщённая с помощью всех этих макросов, будет выглядеть так: template‹class TRet TEMPLATE_PARAMS› class I_DELEGATE { public: virtual ~I_DELEGATE() {} virtual TRet Invoke(PARAMS) = 0; virtual bool Compare(I_DELEGATE‹TRet TEMPLATE_ARGS›* pDelegate) = 0; }; template‹class TRet TEMPLATE_PARAMS› class C_STATIC_DELEGATE: public I_DELEGATE‹TRet TEMPLATE_ARGS› { public: typedef TRet (*PFunc)(PARAMS); C_STATIC_DELEGATE(PFunc pFunc) { m_pFunc = pFunc; } virtual TRet Invoke(PARAMS) { return m_pFunc(ARGS); } virtual bool Compare(I_DELEGATE‹TRet TEMPLATE_ARGS›* pDelegate) { C_STATIC_DELEGATE‹TRet TEMPLATE_ARGS›* pStaticDel = dynamic_cast‹C_STATIC_DELEGATE‹TRet TEMPLATE_ARGS›*›(pDelegate); if (pStaticDel == NULL || pStaticDel-›m_pFunc != m_pFunc) return false; return true; } private: PFunc m_pFunc; }; template‹class TObj, class TRet TEMPLATE_PARAMS› class C_METHOD_DELEGATE: public I_DELEGATE‹TRet TEMPLATE_ARGS› { public: typedef TRet (TObj::*PMethod)(PARAMS); C_METHOD_DELEGATE(TObj* pObj, PMethod pMethod) { m_pObj = pObj; m_pMethod = pMethod; } virtual TRet Invoke(PARAMS) { return (m_pObj-›*m_pMethod)(ARGS); } virtual bool Compare(I_DELEGATE‹TRet TEMPLATE_ARGS›* pDelegate) { C_METHOD_DELEGATE‹TObj, TRet TEMPLATE_ARGS›* pMethodDel = dynamic_cast‹C_METHOD_DELEGATE‹TObj, TRet TEMPLATE_ARGS›*›(pDelegate); if (pMethodDel == NULL || pMethodDel-›m_pObj != m_pObj || pMethodDel-›m_pMethod != m_pMethod) { return false; } return true; } private: TObj *m_pObj; PMethod m_pMethod; }; template‹class TRet TEMPLATE_PARAMS› I_DELEGATE‹TRet TEMPLATE_ARGS›* NewDelegate(TRet (*pFunc)(PARAMS)) { return new C_STATIC_DELEGATE‹TRet TEMPLATE_ARGS›(pFunc); } template ‹class TObj, class TRet TEMPLATE_PARAMS› I_DELEGATE‹TRet TEMPLATE_ARGS›* NewDelegate(TObj* pObj, TRet (TObj::*pMethod)(PARAMS)) { return new C_METHOD_DELEGATE‹TObj, TRet TEMPLATE_ARGS› (pObj, pMethod); } template‹class TRet TEMPLATE_PARAMS› class C_DELEGATE { public: typedef I_DELEGATE‹TRet TEMPLATE_ARGS› IDelegate; typedef std::list‹IDelegate*› DelegateList; C_DELEGATE(IDelegate* pDelegate = NULL) { Add(pDelegate); } ~C_DELEGATE() { RemoveAll(); } bool IsNull() { return (m_DelegateList.empty()); } C_DELEGATE‹TRet TEMPLATE_ARGS›& operator=(IDelegate* pDelegate) { RemoveAll(); Add(pDelegate); return *this; } C_DELEGATE‹TRet TEMPLATE_ARGS›& operator+=(IDelegate* pDelegate) { Add(pDelegate); return *this; } C_DELEGATE‹TRet TEMPLATE_ARGS›& operator-=(IDelegate* pDelegate) { Remove(pDelegate); return *this; } TRet operator()(PARAMS) { return Invoke(ARGS); } private: void Add(IDelegate* pDelegate) { if(pDelegate != NULL) m_DelegateList.push_back(pDelegate); } void Remove(IDelegate* pDelegate) { DelegateList::iterator it; for(it = m_DelegateList.begin(); it!= m_DelegateList.end(); ++it) { if((*it)-›Compare(pDelegate)) { delete (*it); m_DelegateList.erase(it); break; } } } void RemoveAll() { DelegateList::iterator it; for(it = m_DelegateList.begin(); it!= m_DelegateList.end(); ++it) delete (*it); m_DelegateList.clear(); } TRet Invoke(PARAMS) { DelegateList::const_iterator it; for (it = m_DelegateList.begin(); it != --m_DelegateList.end(); ++it) (*it)-›Invoke(ARGS); return m_DelegateList.back()-›Invoke(ARGS); } private: DelegateList m_DelegateList; }; Вынеся обобщённый таким образом делегат в отдельный файл delegate_impl.h, мы можем сгенерировать его специализацию для любого количества параметров. Например, специализация делегата для пяти параметров получается так: // 5 parameters… #define SUFFIX 5 #define TEMPLATE_PARAMS \ , class TP1, class TP2, class TP3, class TP4, class TP5 #define TEMPLATE_ARGS , TP1, TP2, TP3, TP4, TP5 #define PARAMS TP1 p1, TP2 p2, TP3 p3, TP4 p4, TP5 p5 #define ARGS p1, p2, p3, p4, p5 #include "delegate_impl.h" #undef SUFFIX #undef TEMPLATE_PARAMS #undef TEMPLATE_ARGS #undef PARAMS #undef ARGS Подобные фрагменты для наборов от 0 до 10 параметров можно включить в отдельный файл delegate.h, который и будут подключать пользователи делегатов. Вот пример использования библиотеки делегатов, которую мы только что получили. Обратите внимание, что он практически полностью соответствует примеру на языке C#, с которого началась эта статья. #include ‹iostream› #include ‹fstream› #include ‹string› using namespace std; #include "delegate.h" class App { public: // Определяем делегат Callback, // который принимает 1 параметр и ничего не возвращает. typedef CDelegate1‹void, string› Callback; // Это метод класса App. void OutputToConsole(string str) { cout ‹‹ str ‹‹ endl; } // А это статический метод класса App. static void OutputToFile(string str) { ofstream fout("output.txt", ios::out | ios::app); fout ‹‹ str ‹‹ endl; fout.close(); } }; int main() { App app; // Создаём делегат. App::Callback callback = NULL; if (!callback.IsNull()) callback("1"); // Добавляем ссылку на OutputToFile. // Вызываем её через делегата. callback += NewDelegate(App::OutputToFile); if (!callback.IsNull()) callback("2"); // Добавляем ссылку на OutputToConsole. // Вызывается вся цепочка: // сначала OutputToFile, потом OutputToConsole. callback += NewDelegate(&app, &App::OutputToConsole); if (!callback.IsNull()) callback("3"); // Убираем ссылку на OutputToFile. // Вызывается только OutputToConsole. callback -= NewDelegate(App::OutputToFile); if (!callback.IsNull()) callback("4"); // Убираем оставшуюся ссылку на OutputToConsole. callback -= NewDelegate(&app, &App::OutputToConsole); if (!callback.IsNull()) callback("5"); } Законченный проект Visual Studio 7.0, содержащий этот пример, можно найти на сопровождающем компакт-диске. |
|
||
Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх | ||||
|