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 TEMPLATE_PARAMS›
class C_STATIC_DELEGATE_VOID: public I_DELEGATE‹TRet TEMPLATE_ARGS› {
…
virtual DelegateRetVal‹TRet›::Type Invoke(PARAMS) {
m_pFunc(ARGS);
return 0;
}
…
};
template‹class TObj, class TRet TEMPLATE_PARAMS›
class C_METHOD_DELEGATE_VOID: public I_DELEGATE‹TRet TEMPLATE_ARGS› {
…
virtual DelegateRetVal‹TRet›::Type Invoke(PARAMS) {
Читать дальше