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, с которой и начался этот раздел. Таким образом, мы заходим в тупик.
Остаётся два пути. Первый путь - написать отдельную реализацию
CDelegateVoidX, которая будет использоваться вместо
CDelegateX в случае
TRet=void. Этот путь плох, так как приводит к изменению внешнего интерфейса библиотеки делегатов. А это значит, что пользователям библиотеки придётся писать по две разных версии своих программ - для VC6 и для всех остальных компиляторов.
Второй путь - изменить функции
Invoke так, чтобы в случае
TRet=void они возвращали не
void, а какое-нибудь нейтральное значение (например, ноль). Конечно, это не совсем честное решение, но оно вполне работоспособно. Посмотрим, как его можно реализовать.
В первую очередь нам нужен инструмент для преобразования типов, который на этапе компиляции превращал бы
void в
int, а остальные типы оставлял бы без изменений. В C++ такие преобразования типов осуществляются с использованием полной специализации шаблонов (к счастью, её VC6 поддерживает). В нашем случае реализация будет выглядеть так.
template‹class T›
struct DelegateRetVal {
typedef T Type;
};
template‹›
struct DelegateRetVal‹void› {
typedef int Type;
};
Как видим, внутри класса
DelegateRetVal определяется тип
Type, который в общем случае совпадает с параметром шаблона
T. Для случая
T=void это поведение переопределяется с использованием специализации: в этом случае тип
Type определяется как
int. В результате, выражение
DelegateRetVal‹TRet›::Type будет на этапе компиляции принимать нужный нам тип при любых значениях
TRet.Следующий шаг - модификация классов
CStaticDelegateX и
CMethodDelegateX. Во-первых, нужно заменить значение, возвращаемое методом
Invoke, на
DelegateRetVal‹TRet›::Type. Во-вторых, нужно реализовать два дополнительных класса,
CStaticDelegateVoidX и
CMethodDelegateVoidX, для обработки случая
TRet=void. Единственным их отличием от одноимённых классов без суффикса "Void" будет другая реализация метода
Invoke:
#define C_STATIC_DELEGATE_VOID COMBINE(CStaticDelegateVoid, SUFFIX)
#define C_METHOD_DELEGATE_VOID COMBINE(CMethodDelegateVoid, SUFFIX)
…
template‹class TRet
Последние комментарии
5 часов 56 минут назад
5 часов 57 минут назад
17 часов 20 минут назад
17 часов 21 минут назад
19 часов 22 минут назад
19 часов 24 минут назад