Делегаты на C++ [Александр Шаргин] (fb2) читать постранично, страница - 2
[Настройки текста] [Cбросить фильтры]
- 1
- 2
- 3
- 4
- . . .
- последняя (7) »
template ‹class TObj› IDelegateVoid* NewDelegate(TObj* pObj, void (TObj::*pMethod)()) { return new CMethodDelegateVoid‹TObj› (pObj, pMethod); }
Мы уже почти закончили. Осталось написать объектную обёртку над интерфейсом IDelegateVoid, которая будет поддерживать список указателей и определять набор операторов, аналогичных используемым в C# - operator=, operator(), operator+= и operator-=. Для простоты будем использовать стандартный класс std::list для хранения списка указателей. #include ‹list›
class CDelegateVoid { public: CDelegateVoid(IDelegateVoid* pDelegate = NULL) { Add(pDelegate); } ~CDelegateVoid() { RemoveAll(); } bool IsNull() { return (m_DelegateList.size() == 0); } CDelegateVoid& operator=(IDelegateVoid* pDelegate) { RemoveAll(); Add(pDelegate); return *this; } CDelegateVoid& operator+=(IDelegateVoid* pDelegate) { Add(pDelegate); return *this; } CDelegateVoid& operator-=(IDelegateVoid* pDelegate) { Remove(pDelegate); return *this; } void operator()() { Invoke(); } private: void Add(IDelegateVoid* pDelegate); void Remove(IDelegateVoid* pDelegate); void RemoveAll(); void Invoke(); private: std::list‹IDelegateVoid*› m_DelegateList; };
Для реализации необходимого набора операторов используются вспомогательные методы Add, Remove, RemoveAll и Invoke. Метод Add добавляет новый указатель IDelegateVoid* в список: void CDelegateVoid::Add(IDelegateVoid* pDelegate) { if (pDelegate != NULL) m_DelegateList.push_back(pDelegate); }
Метод Remove ищет в списке делегат, ссылающийся на заданную функцию, и в случае обнаружения удаляет его: void CDelegateVoid::Remove(IDelegateVoid* pDelegate) { std::list‹IDelegateVoid*›::iterator it; for(it = m_DelegateList.begin(); it!= m_DelegateList.end(); ++it) { if((*it)-›Compare(pDelegate)) { delete (*it); m_DelegateList.erase(it); break; } } delete pDelegate; }
Метод RemoveAll просто очищает список, удаляя из него все делегаты: void CDelegateVoid::RemoveAll() { std::list‹IDelegateVoid*›::iterator it; for(it = m_DelegateList.begin(); it != m_DelegateList.end(); ++it) delete (*it); m_DelegateList.clear(); }
Наконец, метод Invoke вызывает все функции и методы, на которые ссылаются делегаты из списка: void CDelegateVoid::Invoke() { std::list‹IDelegateVoid*›::const_iterator it; for (it = m_DelegateList.begin(); it != m_DelegateList.end(); ++it) (*it)-›Invoke(); }
Использовать полученный класс делегата можно примерно так. void Global() { std::cout ‹‹ "Global" ‹‹ std::endl; }
class C { public: void Method() { std::cout ‹‹ "Method" ‹‹ std::endl; } static void StaticMethod() { std::cout ‹‹ "StaticMethod" ‹‹ std::endl; } };
int main() { C c; CDelegateVoid delegate = NewDelegate(Global); delegate += NewDelegate(&c, &C::Method); delegate += NewDelegate(C::StaticMethod); delegate(); // вызывается Global, Method и StaticMethod. delegate -= NewDelegate(C::StaticMethod); delegate -= NewDelegate(Global); delegate(); // вызывается только Method. return 0; }
Как видим, класс CDelegateVoid очень похож на делегаты из C#. Он полностью типобезопасен, так как попытка передать функции NewDelegate ссылку на функцию или метод, сигнатура которых отличается от void(void), немедленно приведёт к ошибке. Реализация класса CDelegateVoid не использует никаких предположений о размере и устройстве указателя на метод класса, поэтому он может использоваться как при обычном, так и при множественном и виртуальном наследовании. Его можно без изменений переносить на новые платформы и компиляторы.
Общая реализация
Теперь посмотрим, как можно обобщить класс 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 макросами. Эти различающиеся участки можно
- 1
- 2
- 3
- 4
- . . .
- последняя (7) »
Последние комментарии
1 час 28 минут назад
4 часов 2 минут назад
4 часов 30 минут назад
4 часов 37 минут назад
6 часов 12 минут назад
7 часов 40 минут назад