Новый способ реализовать делегаты с помощью C++ - Использование кода

ОГЛАВЛЕНИЕ

Использование кода

Следующий фрагмент кода показывает использование этой библиотеки:

using namespace sophia;

// Базовый класс с виртуальным методом
struct BaseClass
{
   virtual int virtual_method(int param) const
   {
       printf("We are in BaseClass: (param = %d)\n", param);
       return param;
   }

   char relaxed_method(long param)
   {
       printf("We are in relaxed_method: (param = %d)\n", param);
       return 0;
   }
};

// класс с виртуальным наследованием
struct DerivedClass : public virtual BaseClass {
   virtual int virtual_method(int param) const
   {
       printf("We are in DerivedClass: (param = %d)\n", param);
       return param;
   }
};

void Test()
{
   // Предполагаем, что у нас есть несколько объектов
   DerivedClass object;

   // объявление делегатов
   typedef sophia::delegate0<DWORD> MyDelegate0;
   typedef sophia::delegate1<int, int> MyDelegate1;
   typedef sophia::delegate4<void, int, int, long, char> AnotherDelegateType;

   // определение размера экземпляра делегата
   printf("sizeof(delegate) = %d\n", sizeof(AnotherDelegateType));

   // Конструктор
   MyDelegate0 d0(&GetCurrentThreadId);
   MyDelegate1 d1(&object, &DerivedClass::virtual_method);
   MyDelegate1 d2; // нулевой делегат
   AnotherDelegateType dNull;

   // Сравниваем делегаты между собой, даже если они имеют разные типы
   assert(d2 == dNull);

   // связываем со свободной функцией или методом
   d0.bind(&GetCurrentThreadId);
   d0 = &GetCurrentThreadId;
   d2.bind(&object, &DerivedClass::virtual_method);

   // снова сравниваем после связывания
   assert(d2 == d1);

   // освобождаем делегат
   d2 = NULL; // or
   d2.clear();

   // вызов с помощью синтаксиса 01
   d1(1000);

   // вызов с помощью синтаксиса 02
   // этот синтаксис быстрее, чем синтаксис 01
   (*d1)(10000);

   // делегат RELAXED
   d1.bind(&object, &DerivedClass::relaxed_method);
   (*d1)(10000);

   // обмен между двумя делегатами
   d2.swap(d1); // now d1 == NULL

   // выполняем нулевой/пустой делегат
   assert(d1.empty());
   try
   {
       d1(100);
   }
   catch(sophia::bad_function_call& e)
   {
       printf("Exception: %s\n    Try again: ", e.what());
       d2(0);
   }

   // управление временем существования объекта
   // Случай 1: мы хотим, чтобы объект был клонирован
   d1.bind(&object, &DerivedClass::virtual_method,
       clone_option<heap_clone_allocator>(true));

   // управление временем существования объекта
   // Случай 2: мы не хотим, чтобы объект клонировался при связывании
   for(int i=0; i<100; ++i)
   {
       DerivedClass* pObject = new DerivedClass();
       d1.bind(pObject, &DerivedClass::virtual_method,
           clone_option<heap_clone_allocator>(false));
       d1(100);
   }

}

Сравнение производительности

Все делегаты, включая и этот, добавляют 2 дополнительных слоя перед вызовом реального метода или свободной функции.

  • Первое, параметры передаются делегату.
  • Второе, параметры передаются Stub, Invoker или Стратегии (эта библиотека).
  • Третье, параметры передаются реальному методу или функции.

Этот делегат показывает способ убрать один слой:

  • Синтаксис 01 использует 2 дополнительных слоя, так же, как и другие
    D(param1, param2)
  • Синтаксис 02 использует оператор разыменования, чтобы непосредственно вызвать объект стратегии
    (*D)(param1, param2)

Если мы передаем простые типы данных (char, int, указатель) любой из перечисленных реализаций делегатов, компилятор сгенерирует оптимизирующий код. Так что их скорости вызова не сильно различаются между собой. Но если мы передаем сложные типы данных, которые используют конструктор и деструктор, то 2й синтаксис будет выполняться быстрее любого другого.

Интересные особенности

  • Эта статья показывает способ преобразования  различных форматов указателя на метод/свободную функцию в унифицированный формат; преобразование выполняется совместимым со стандартом способом. В результате сравнение между экземплярами делегатов выполняется легко.
  • Стратегический шаблон проекта с небольшим изменением делает этот делегат быстрым и расширяемым.
  • Люди с небольшим опытом в программировании шаблонов на C++ могут понять исходный код.

Что дальше?

Групповой делегат

В этой статье обсуждался делегат типа Singlecast. Есть другой тип, так называемый делегат Multicast, внутренне являющийся коллекцией других делегатов. Когда мы вызываем Multicast, все делегаты Singlecast в коллекции также вызываются один за другим. В основном Multicast используется для реализации Шаблона проекта наблюдателя (Observer Design Pattern).

Более переносимый

Здесь приведен список компиляторов, с которыми тестировалась библиотека:

  • VC++ 7.1
  • VC++ 2005
  • Mingw32-gcc-3.4.5
Автор: Quynh Nguyen

Загрузить исходный код и демо-проект - 9.7 KB