Читать «Делегаты на C++» онлайн - страница 9
Александр Шаргин
Чтобы разрешить эту неоднозначность, придётся ввести дополнительный параметр функции NewDelegate, по которому и будет выбираться нужная версия функции:
// Параметр этого типа будет индикатором
template‹int use›
class UseVoid {};
…
template‹class TRet TEMPLATE_PARAMS›
I_DELEGATE‹TRet TEMPLATE_ARGS›* NewDelegate(TRet (*pFunc)(PARAMS), UseVoid‹0›) {
return new C_STATIC_DELEGATE‹TRet TEMPLATE_ARGS›(pFunc);
}
template‹class TRet TEMPLATE_PARAMS›
I_DELEGATE‹TRet TEMPLATE_ARGS›* NewDelegate(TRet (*pFunc)(PARAMS), UseVoid‹1›) {
return new C_STATIC_DELEGATE_VOID‹TRet TEMPLATE_ARGS›(pFunc);
}
Тем самым мы избавляемся от неоднозначности. Но возникает другая проблема. Теперь при вызове NewDelegate необходимо явно указывать, какая версия функции нам нужна:
void f();
int g();
…
NewDelegate(f, UseVoid‹1›());
NewDelegate(g, UseVoid‹0›());
Чтобы избавиться от необходимости явно указывать параметр UseVoid, напишем третий вариант функции NewDelegate, который будет автоматически (причём на этапе компиляции) определять и вызывать нужную версию этой функции. Для реализации этой идеи нам потребуется механизм преобразования типа TRet в константу 1 (в случае TRet=void) или 0 (для всех остальных типов). Мы уже решали аналогичную задачу в классе DelegateRetVal, поэтому теперь решение записывается без труда:
template‹class T›
struct IsVoid {
enum { Result = 0};
};
template‹› struct
IsVoid‹void› {
enum {Result = 1};
};
Теперь воспользуемся классом IsVoid для выбора нужного варианта функции NewDelegate.
template‹class TRet TEMPLATE_PARAMS›
I_DELEGATE‹TRet TEMPLATE_ARGS›* NewDelegate(TRet (*pFunc)(PARAMS)) {
return NewDelegate(pFunc, UseVoid‹IsVoid‹TRet›::Result›());
}
Аналогичным образом NewDelegate перегружается для случая создания объектов CMethodDelegate*:
I_DELEGATE‹TRet TEMPLATE_ARGS›* NewDelegate(TObj* pObj, TRet (TObj::*pMethod)(PARAMS), UseVoid‹0›) {
return new C_METHOD_DELEGATE‹TObj, TRet TEMPLATE_ARGS› (pObj, pMethod);
}
template ‹class TObj, class TRet TEMPLATE_PARAMS›
I_DELEGATE‹TRet TEMPLATE_ARGS›* NewDelegate(TObj* pObj, TRet (TObj::*pMethod)(PARAMS), UseVoid‹1›) {
return new C_METHOD_DELEGATE_VOID‹TObj, TRet TEMPLATE_ARGS› (pObj, pMethod);
}
template ‹class TObj, class TRet TEMPLATE_PARAMS›
I_DELEGATE‹TRet TEMPLATE_ARGS›* NewDelegate(TObj* pObj, TRet (TObj::*pMethod)(PARAMS)) {
return NewDelegate(pObj, pMethod, UseVoid‹IsVoid‹TRet›::Result›());
}
Если вас успели утомить эти "хождения по мукам", у меня есть для вас хорошая новость. Проблема, которую мы только что решили, была последней. Осталось заменить возвращаемые значения методов Invoke и operator() в классе CDelegate на DelegateRetVal‹TRet›::Type, чтобы получить законченную реализацию делегатов для Visual C++ 6.0.
Полную версию реализации делегатов для Visual C++ 6.0 можно найти на сопровождающем компакт-диске.
Больше, лучше, быстрее
Реализация делегатов, которую мы рассмотрели выше, вполне работоспособна. Тем не менее, некоторые её особенности вызывают озабоченность. Во-первых, интенсивное использование шаблонов может привести к чрезмерному разбуханию кода. Во-вторых, объекты делегатов распределяются динамически (при помощи оператора new). Поскольку на создание объектов в куче тратится гораздо больше времени, чем на создание стековых объектов, это может привести к проблемам производительности. В этом разделе мы рассмотрим некоторые пути преодоления этих проблем.