Делегаты на C++ - страница 5
>#include ‹iostream›
>#include ‹fstream›
>#include ‹string›
>using namespace std;
>#include "delegate.h"
>class App {
>public:
> // Определяем делегат Callback,
> // который принимает 1 параметр и ничего не возвращает.
> typedef CDelegate1‹void, string› Callback;
> // Это метод класса App.
> void OutputToConsole(string str) { cout ‹‹ str ‹‹ endl; }
> // А это статический метод класса App.
> static void OutputToFile(string str) {
> ofstream fout("output.txt", ios::out | ios::app);
> fout ‹‹ str ‹‹ endl; fout.close();
> }
>};
>int main() {
> App app;
> // Создаём делегат.
> App::Callback callback = NULL;
> if (!callback.IsNull()) callback("1");
> // Добавляем ссылку на OutputToFile.
> // Вызываем её через делегата.
> callback += NewDelegate(App::OutputToFile);
> if (!callback.IsNull()) callback("2");
> // Добавляем ссылку на OutputToConsole.
> // Вызывается вся цепочка:
> // сначала OutputToFile, потом OutputToConsole.
> callback += NewDelegate(&app, &App::OutputToConsole);
> if (!callback.IsNull()) callback("3");
> // Убираем ссылку на OutputToFile.
> // Вызывается только OutputToConsole.
> callback -= NewDelegate(App::OutputToFile);
> if (!callback.IsNull()) callback("4");
> // Убираем оставшуюся ссылку на OutputToConsole.
> callback -= NewDelegate(&app, &App::OutputToConsole);
> if (!callback.IsNull()) callback("5");
>}
Законченный проект Visual Studio 7.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 и для всех остальных компиляторов.