Делегаты на C++ - [4]

Шрифт
Интервал

>  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, содержащий этот пример, можно найти на сопровождающем компакт-диске.

Те же и Visual C++ 6.0

На этом можно было бы поставить точку, но остаётся ещё одна нерешённая проблема. Если вы попытаетесь скомпилировать приведённый пример в Visual C++ 6.0, у этого компилятора возникнут проблемы при задании параметра шаблона делегата TRet=void. Дело в том, что в этом случае VC6 не может корректно обработать конструкцию вида:

>virtual TRet Invoke(TP1 p1) {

> // VC6 полагает, что нельзя возвращать выражение типа void.

> return (m_pObj-›*m_pMethod)(p1);

>}


Данная конструкция совершенно законна в соответствии с пунктом 6.6.3/3 Стандарта языка C++. Но VC6 об этом не знает. Поэтому нам придётся искать обходные пути. Чтобы обойти эту недоработку компилятора, необходимо отдельно реализовать все классы CDelegateX для случая TRet=void. Идеальным инструментом для этой цели служит частичная специализация шаблонов, но VC6 не поддерживает и эту возможность языка C++. В результате решение задачи на VC6 превращается в занимательную головоломку.

Первой моей мыслью было воспользоваться техникой, описанной Павлом Кузнецовым в этом же номере журнала в статье "Симуляция частичной специализации". К сожалению, выяснилось, что эта методика неприменима для реализации делегатов на VC6 сразу по двум причинам. Первая причина состоит в том, что использование полиморфизма совместно с навороченными шаблонными конструкциями оказывается "не по зубам" VC6, и он отказывается компилировать классы CStaticDelegateX и CMethodDelegateX, переписанные с использованием частичной специализации. На самом деле, это ещё полбеды, так как эти классы являются внутренней деталью реализации, и применять к ним частичную специализацию не обязательно. Вторая причина носит более фундаментальный характер. Дело в том, что симуляция частичной специализации для класса CDelegate подразумевает создание двух базовых классов (например, CDelegate_void_ для случая TRet=void и CDelegate_ для всех остальных случаев). Затем, в зависимости от значения параметра TRet, класс CDelegate наследовался бы либо от общей, либо от частной реализации. И тут возникает проблема. Дело в том, что в языке C++ операторы не наследуются. Это означает, что operator() нам всё равно придётся реализовывать в классе CDelegate. А мы не сможем реализовать его из-за той самой ошибки VC6, с которой и начался этот раздел. Таким образом, мы заходим в тупик.

Остаётся два пути. Первый путь - написать отдельную реализацию


Рекомендуем почитать
Изучаем Java EE 7

Java Enterprise Edition (Java EE) остается одной из ведущих технологий и платформ на основе Java. Данная книга представляет собой логичное пошаговое руководство, в котором подробно описаны многие спецификации и эталонные реализации Java EE 7. Работа с ними продемонстрирована на практических примерах. В этом фундаментальном издании также используется новейшая версия инструмента GlassFish, предназначенного для развертывания и администрирования примеров кода. Книга написана ведущим специалистом по обработке запросов на спецификацию Java EE, членом наблюдательного совета организации Java Community Process (JCP)


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Обработка событий в С++

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Программное обеспечение встроенных систем. Общие требования к разработке и документированию

Embedded system software. General requirements for development and documentationСтандарт подготовлен в развитие ГОСТ Р ИСО/МЭК 12207-99 «Информационная технология. Процессы жизненного цикла программных средств» с целью учета специфики разработки и документирования программного обеспечения встроенных систем реального времени.


Как пасти котов. Наставление для программистов, руководящих другими программистами

«Как пасти котов» – это книга о лидерстве и руководстве, о том, как первое совмещать со вторым. Это, если хотите, словарь трудных случаев управления IT-проектами. Программист подобен кошке, которая гуляет сама по себе. Так уж исторически сложилось. Именно поэтому так непросто быть руководителем команды разработчиков. Даже если вы еще месяц назад были блестящим и дисциплинированным программистом и вдруг оказались в роли менеджера, вряд ли вы знаете, с чего надо начать, какой выбрать стиль руководства, как нанимать и увольнять сотрудников, проводить совещания, добиваться своевременного выполнения задач.