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

ОГЛАВЛЕНИЕ

Предпосылки

Различные виды классов

// kind_of_class.cpp
// Этот файл демонстрирует различные виды указателя на члены класса

class dummy_base1 { };
class dummy_base2 { };

class dummy_s : dummy_base1 { };
// Дойдя сюда, компилятор распознает dummy_s как
// вид "одиночного наследования".
typedef void (dummy_s::*pointer_to_dummy_s)(void);
size_t size_of_single = sizeof(pointer_to_dummy_s);

class dummy_m : dummy_base1, dummy_base2 { };
// Дойдя сюда, компилятор распознает dummy_s как
// вид "множественного наследования".
typedef void (dummy_m::*pointer_to_dummy_m)(void);
size_t size_of_multi = sizeof(pointer_to_dummy_m);

class dummy_v : virtual dummy_base1 { };
// Дойдя сюда, компилятор распознает dummy_s как
// вид "виртуального наследования".
typedef void (dummy_v::*pointer_to_dummy_v)(void);
size_t size_of_virtual = sizeof(pointer_to_dummy_v);

class dummy_u;
// опережающая ссылка, неизвестная в этот момент
typedef void (dummy_u::*pointer_to_dummy_u)(void);
size_t size_of_unknown = sizeof(pointer_to_dummy_u);

void main()
{
    printf("%d\n%d\n%d\n%d", size_of_single, size_of_multi,
        size_of_virtual, size_of_unknown);
}

Если вы компилируете и запускаете пример с помощью VC++, используя настройки проекта по умолчанию, вывод будет такой:

4
8
12
16

Вывод вышеприведенного примера не будет идентичным при компиляции с помощью других компиляторов C++. Дополнительную информацию смотрите в разделе статьи Дона, озаглавленном "Реализации указателей на функции-члены класса". Очевидно, что указатель на метод класса "unknown (неизвестный)" всегда имеет самый большой размер, за исключением компиляторов. Отныне мы будем разделять классы C++ на 2 категории:

  • Известный класс (одиночный, множественный, Виртуальный)
  • Неизвестный класс (предваряющее объявление)

Различные виды методов одного и того же класса

Ниже указаны несколько атрибутов (модификаторов), которые влияют на методы класса:

  • Соглашение о вызовах: __cdecl, __stdcall, __fastcall, __thiscall. Обратитесь к статье «Передача параметров и соглашение об именах» за более подробной информацией. C++ позволяет, но игнорирует соглашения о вызовах для объявлений методов
  • Число и типы аргументов
  • Тип возвращаемого значения
  • Постоянные методы против непостоянных
  • Виртуальные методы против невиртуальных

К счастью, ни один из этих атрибутов (модификаторов) не влияет на размер указателя на метод. Предположим, что CKnownClass является известным классом, который уже был где-то описан; здесь приводится пример:

// __thiscall, непостоянный, 1 аргумент
typedef void (CKnownClass::*method_type_1)(int );

// __cdecl, непостоянный, 2 аргумента, возвращает CString
typedef CString (__cdecl CKnownClass:: method_type_2)(int, CString );

// __fastcall, постоянный, 3 аргумента, возвращает void*
typedef void* (__fastcall CKnownClass::* method_type_3)(
    int, CString, void*) const;

void main()
{
    printf("%d\n%d\n%d", sizeof(method_type_1),
        sizeof(method_type_2),
        sizeof(method_type_3));
}

Различные виды свободных функций или статических методов

Ниже указаны несколько атрибутов (модификаторов), которые влияют на свободные функции или статические методы:

  • Соглашение о вызовах: __cdecl, __stdcall, __fastcall
  • Число и тип аргументов
  • Тип возвращаемого значения

Аналогично, атрибуты не влияют на размер указателей на свободные функции. Размер должен быть равен 4 байтам на всех 32-битных платформах и должен быть равен 8 на всех 64-битных платформах.