Делегаты на C++ - страница 8

стр.

шаблоны не используются. Теперь в терминах этого интерфейса можно реализовать базовый класс CDelegateImpl, который будет отвечать за поддержку списка делегатов. Соответственно, в нём будут реализованы методы Add, Remove и Invoke.

>class CDelegateImpl {

>public:

> typedef std::list‹IComparableDelegate*› DelegateList;

> CDelegateImpl(IComparableDelegate* pDelegate = NULL) { Add(pDelegate); }

> ~CDelegateImpl() { RemoveAll(); }

> bool IsNull() { return (m_DelegateList.empty()); }

>protected:

> void Add(IComparableDelegate* pDelegate) {

>  if (pDelegate != NULL) m_DelegateList.push_back(pDelegate);

> }

> void Remove(IComparableDelegate* 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();

> }

>protected:

> DelegateList m_DelegateList;

>};


Теперь реализация класса CDelegateX существенно упрощается. В нём останутся только операторы (для которых используется inline-подстановка) и метод Invoke. Только этот метод и будет сгенерирован отдельно для каждой специализации - хороший результат по сравнению с тем, что было раньше. Новая реализация класса CDelegateX будет выглядеть так:

>template‹class TRet TEMPLATE_PARAMS›

>class C_DELEGATE: public CDelegateImpl {

>public:

> typedef I_DELEGATE‹TRet TEMPLATE_ARGS› IDelegate;

> C_DELEGATE(IDelegate* pDelegate = NULL): CDelegateImpl(pDelegate) {}

> 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:

> TRet Invoke(PARAMS) {

>  DelegateList::const_iterator it;

>  for (it = m_DelegateList.begin(); it!= --m_DelegateList.end(); ++it) static_cast‹IDelegate*› (*it)-›Invoke(ARGS);

>  return static_cast‹IDelegate*› (m_DelegateList.back())-›Invoke(ARGS);

> }

>};


Обратите внимание на появившиеся приведения типов. В данном случае они никак не сказываются на типобезопасности делегатов, так как в списке m_DelegateList могут храниться только указатели на объекты классов CStaticDelegateX и CMethodDelegateX, а эти указатели заведомо приводятся к указателю на IDelegate.

В заключение несколько слов о проблеме производительности. Как уже говорилось, она может возникать из-за распределения объектов делегатов в куче. К сожалению, реализовать делегаты как стековые объекты не представляется возможным, так как для них существенным свойством является полиморфное поведение. Но и тут ситуацию можно существенно улучшить. Поскольку все делегаты централизованно создаются внутри функции NewDelegate, для них вполне возможно написать специализированный аллокатор, который будет распределять память для делегатов быстро и эффективно. Написание такого аллокатора оставляется читателю в качестве упражнения.

Заключение

Хочется отметить, что рассмотренный нами пример реализации делегатов может служить иллюстрацией как сильных, так и слабых сторон языка C++. Слабая сторона C++ - это его сложность. Особенно хорошо она заметна при реализации библиотек на базе шаблонов. Их код трудно читать и ещё труднее писать, так как в них семантическая сложность усугубляется сложностью синтаксической. Сильной же стороной C++ является совершенно невероятная гибкость этого языка. В рамках C++ можно реализовать и бесшовно интегрировать в язык самые разные возможности. Причём сделать это удаётся даже несмотря на грубейшие ошибки и недоработки разработчиков некоторых компиляторов.


Комментарии:

наследование операторов

›Дело в том, что в языке C++ операторы не наследуются.

Это не верно по крайней мере для MSVC++. Более того этот метод используется при написании функтора из библиотеки Loki