Читать «Делегаты на C++» онлайн - страница 8
Александр Шаргин
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) {
(m_pObj-›*m_pMethod)(ARGS);
return 0;}
…
};
ПРИМЕЧАНИЕ В этом месте может возникнуть соблазн избежать дублирования кода, породив класс CStaticDelegateVoidX от CStaticDelegateX и CMethodDelegateVoidX от CMethodDelegateX соответственно. К сожалению, это не будет работать. Хотя мы и переопределяем виртуальный метод Invoke в производных классах, теоретическая возможность обратиться к Invoke базовых классов сохраняется. Поэтому компилятор честно попытается сгенерировать их реализацию. А это в случае TRet=void в очередной раз приведёт к ошибке, которую мы пытаемся обойти. Поэтому дублирование кода в данном случае неизбежно.
Осталось сделать последний шаг - перегрузить функцию NewDelegate ещё двумя реализациями:
template‹class TRet TEMPLATE_PARAMS›
I_DELEGATE‹TRet TEMPLATE_ARGS›* NewDelegate(TRet (*pFunc)(PARAMS)) {
return new C_STATIC_DELEGATE‹TRet TEMPLATE_ARGS›(pFunc);
}
template‹class TRet TEMPLATE_PARAMS›
I_DELEGATE‹TRet TEMPLATE_ARGS›* NewDelegate(void (*pFunc)(PARAMS)) {
return new C_STATIC_DELEGATE_VOID‹void TEMPLATE_ARGS›(pFunc);
}
// Аналогично для CMethodDelegate*
В этом месте нас поджидает ещё один сюрприз. В большинстве случаев этот код будет работать, как по маслу. Но при задании TRet=void возникнет неоднозначность при обращении к функции NewDelegate. Правила разрешения перегрузки шаблонов функций описаны в разделе 14.5.5.2 Стандарта языка C++. В соответствии с этими правилами вторая версия NewDelegate не считается более специализированной, чем первая, так как для вызова обоих вариантов функции не требуется неявных преобразований типа.