Читать «Делегаты на 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 не считается более специализированной, чем первая, так как для вызова обоих вариантов функции не требуется неявных преобразований типа.