Те же и Visual C++ 6.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 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 не считается более специализированной, чем первая, так как для вызова обоих вариантов функции не требуется неявных преобразований типа.
Чтобы разрешить эту неоднозначность, придётся ввести дополнительный параметр функции 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 можно найти на сопровождающем компакт-диске.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Visual Basic 6.0
Visual Basic 6.0 В Visual Basic 6.0 в отличие от пятой версии окна Code и Object появляются в нормальном, не в раскрытом виде. И приходится при каждом запуске VB6 раскрывать эти окна. Можно заставить автоматически раскрывать эти окна при каждом запуске. Создайте в соответствующем разделе
Visual Studia.Net
Visual Studia.Net Версия .NET FrameworkНа одном компьютере могут быть установлены несколько версий .NET Framework. Чтобы определить, какие именно версии установлены, нужно просмотреть подразделы в разделе HKLMSoftwareMicrosoft.NETFrameworkpolicyОчистка списка существующих проектовЧтобы очистить список
Visual Basic
Visual Basic Регистрация dll- и ocx-файловДанная заметка будет полезна разработчикам. Если вам часто приходится работать с ocx-файлами (а также с dll-файлами), которые требуют регистрации в реестре с помощью REGSVR.EXE, то будет гораздо удобнее внести соответствующую запись в реестр, чем
Delphi и Visual Basic
Delphi и Visual Basic 1. Есть ли в Delphi эквивалент массива элементов управления из Visual Basic? Hет. Компоненты Delphi не имеют свойства Index, подобное VB. Однако, имеются три основные причины, почему вы хотите использовать их в VB, и для каждой из них есть решение в Delphi. Причина 1. Вы хотите
Создание приложения в среде MS Visual C++ 5.0
Создание приложения в среде MS Visual C++ 5.0 Перед началом работы необходимо скопировать файлы glut.h, glut32.lib glut32.dll в каталоги…MSVCIncludeGl,…MSVCLib,…WindowsSystem соответственно. Также в этих каталогах надо проверить наличие файлов gl.h, glu.h, opengl32.lib, glu32.lib, opengl32.dll, glu32.dll, которые обычно входят в
3. Переменные Visual Basic
3. Переменные Visual Basic В Visual Basic переменные накапливают информацию (значения). При их применении Visual Basic занимают область в памяти компьютера, которая предназначена для сохранения этой информации. Имена переменных, составленные из символов, могут иметь длину в 255 символов.
Что визуального в Visual Basic для приложений?
Что визуального в Visual Basic для приложений? К счастью, VBA во многом избавляет от необходимости нудного печатания программного кода. В одних случаях вы записываете команды, которые нужны в приложении, и используете их в качестве отправной точки при создании новой программы.В
VBA против Visual Basic
VBA против Visual Basic Помимо того, что VBA не позволяет вам создавать отдельные приложения, одно из основных отличий состоит в том, что программы, написанные на VBA, работают медленнее, чем программы, написанные на Visual
9.3. Редактор Visual Basic
9.3. Редактор Visual Basic После записи рассмотренного выше макроса к шаблону Normal добавился следующий текст макроса: Sub Arial_10_bold() “ “ Arial_10_bold Macro “ Arial 10 Полужирный “ With Selection.Font .Name = "Arial" .Size = 10 .Bold = True .Italic = False .Underline = wdUnderlineNone .UnderlineColor = wdColorAutomatic .StrikeThrough = False .DoubleStrikeThrough = False .Outline =
Подход Visual Basic 6.0
Подход Visual Basic 6.0 Благодаря искреннему желанию насладиться более простой жизнью, многие программисты ушли от "мира каркасов" приложений на базе C(++) к более дружественным языкам, таким, как, например, Visual Basic 6.0 (VB6). Язык VB6 стал популярным благодаря тому, что он дает
Возможности Visual Studio 2005
Возможности Visual Studio 2005 Как и следует ожидать, Visual Studio 2005 содержит все необходимые средства проектирования, средства доступа к базам данных, утилиты обзора проектов и объектов, а также интегрированную систему справки. Но, в отличие от средств разработки, которые были
Среда разработки eMbedded Visual C++ 3.0
Среда разработки eMbedded Visual C++ 3.0 Несмотря на то, что среда eVC предназначена для разработки программ для «маленьких» компьютеров, сама среда – вполне серьезный инструмент. Знакомство со средой стоит начать со структуры экрана.ОкнаГлавное окно среды представляет собой окно
Отличия eVB и Visual Basic для. NET
Отличия eVB и Visual Basic для. NET В основном отличия VB.NET от eVB связаны с интеграцией языка VB с Compact Framework и определяются именно внутренней структурой самой CF.Типы данныхВ eVB был один-единственный тип данных на все случаи жизни, Variant, который достался eVB по наследству от VB Script. Visual
Что нового в Visual Studio .NET 2005
Что нового в Visual Studio .NET 2005 Если у вас уже был опыт программирования под Visual Studio .NET 2003, то вы заметите, что на панели инструментов появились новые элементы управления. Они будут рассматриваться в главе, посвященной элементам управления. При разработке дизайна приложения
Microsoft Visual C++
Microsoft Visual C++ Существует несколько версий компилятора Microsoft Visual C++. Наибольшее распространение получили версии 1.51, 2.0 и 2.2. В начале 1996 года появилась новая версия Visual C++ 4.0.Microsoft Visual C++ версии 1.51 содержит только 16-разрядный компилятор. Он позволяет разрабатывать программы для
Microsoft Visual C++ версия 4.0
Microsoft Visual C++ версия 4.0 В конце 1995 года появилась новая версия Microsoft Visual C++ – 4.0. Этот компилятор интегрирован в среду Microsoft Developer Studio , в состав которого могут входить другие программные продукты Microsoft. Интегрированная среда разработчика объединяет одной оболочкой Microsoft Visual C++