====== Реализация интерфейсов в GNU Ada и C++ ====== Объект представлен в памяти некоторой структурой данных. Первый элемент - это указатель на таблицу адресов процедур и функций, реализующих допустимые операции над объектом. Такую таблицу обычно называют таблицей виртуальных функций (ТВФ) или [[http://en.wikipedia.org/wiki/Vtable | vtable]]. При множественном наследовании создается несколько ТВФ. Структура данных объекта содержит по дополнительному указателю на ТВФ для каждого ответвления в дереве наследования интерфейсов. Ниже рассматривается компилятор [[http://gcc.gnu.org/gcc-4.1/ | GCC версии 4.1.1]]. ---- ===== GNU C++ ===== Рассмотрим класс Gamma, наследующий два интерфейса Alpha и Beta. class Alpha { public: int данные_alpha; virtual void методы_alpha (); }; class Beta { public: int данные_beta; virtual void методы_beta (); }; class Gamma : public Alpha, public Beta { public: int данные_gamma; virtual void методы_gamma (); }; Полный исходный текст и полученный код на ассемблере приведены [[gamma-cpp | на отдельной странице]]. Объект типа Gamma имеет структуру: | указатель на ТВФ Alpha+Gamma | <- адрес объекта типа Gamma или Alpha | | данные Alpha | | указатель на ТВФ Beta | <- адрес объекта типа Beta | | данные Beta | | данные Gamma | Для класса Gamma в сегменте кода (readonly) создается таблица-диспетчер: | 0 - смещение к началу | | указатель на описатель типа | | методы Alpha | <- ТВФ Alpha+Gamma | | методы Beta | | методы Gamma | | -8 - смещение к началу | | указатель на описатель типа | | методы-переходники Beta (thunks) | <- ТВФ Beta | Методы-переходники (thunks) представляют собой небольшие дополнительные функции, которые корректируют указатель на объект (добавляя смещение к началу) и затем вызывают основные функции-методы. Описатель типа нужна для конструкций **//typeid//** и **//dynamic_cast//**. Его можно отключить флагом **-fno-rtti**. ---- ===== GNU Ada ===== В языке Ada95 объектам соответствуют структуры типа **//tagged record//**. В версии Ada2005 появились интерфейсы - **//interface//**. При множественном наследовании разрешается только один тип tagged record, остальные должны быть интерфейсами. Хорошая статья на эту тему - [[http://www.adacore.com/2006/06/02/the-implementation-of-ada-2005-interface-types-in-the-gnat-compiler/ | "The Implementation of Ada 2005 Interface Types in the GNAT Compiler"]]. Рассмотрим класс Gamma, наследующий класс Alpha и интерфейс Beta. type Alpha is abstract tagged record data_alpha : Integer; end record; procedure func_alpha (x: in out Alpha) is abstract; type Beta is interface; procedure func_beta (x: in out Beta) is abstract; type Gamma is new Alpha and Beta with record data_gamma : Integer; end record; procedure func_alpha (x: in out Gamma); procedure func_beta (x: in out Gamma); procedure func_gamma (x: in out Gamma); Полный исходный текст и полученный код на ассемблере приведены [[gamma-ada | на отдельной странице]], с [[gamma-ada-asm | подробным описанием]] порожденного кода. Объект типа Gamma имеет структуру: | указатель на ТВФ Alpha+Gamma | <- адрес объекта типа Gamma или Alpha | | данные Alpha | | указатель на ТВФ Beta(Gamma) | <- адрес объекта типа Beta | | данные Gamma | Компилятор создает функцию pkg_elabs() - elaboration function. Она инициализирует таблицы-диспетчеры и описатели типов. Ее следует вызывать до первого обращения к функциям данного пакета. Для типа **Gamma** таблица-диспетчер pkg_gammaT выглядит так: | 00000201h --- дескриптор таблицы | | 0 --- смещение к началу | | &pkg_gammaB --- указатель на описатель типа | | &pkg_size_3 --- метод Gamma'Size | <- ТВФ Gamma | | &pkg_alignment_3 --- метод Gamma'Alignment | | 0 | | 0 | | 0 | | 0 | | &pkg_Oeq_3 --- метод Gamma."=" | | &pkg_assign_3 --- метод Gamma.":=" | | 0 | | 0 | | 0 | | 0 | | 0 | | 0 | | 0 | | &pkg_func_alpha_2 --- метод Gamma.func_alpha | | &pkg_func_beta_2 --- метод Gamma.func_beta | | &pkg_func_gamma --- метод Gamma.func_gamma | Для типа **Gamma с интерфейсом Beta** таблица-диспетчер pkg_T281s выглядит так: | 00000301h --- дескриптор таблицы | | 8 --- смещение к началу | | &pkg_T282s --- указатель на описатель типа | | &pkg_T539s --- переходник Gamma'Size | <- ТВФ Beta(Gamma) | | &pkg_T548s --- переходник Gamma'Alignment | | 0 | | 0 | | 0 | | 0 | | &pkg_T557s --- переходник Gamma."=" | | &pkg_T566s --- переходник Gamma.":=" | | 0 | | 0 | | 0 | | 0 | | 0 | | 0 | | 0 | | &pkg_T578s --- переходник Gamma.func_beta | Поскольку типы Alpha и Beta - абстрактные, объекты этих типов не могут быть созданы. Поэтому [[gamma-ada-unused | таблицы-диспетчеры pkg_alphaT и pkg_betaT]] в процессе выполнения программы не используются. ---- ===== Совместимость между C++ и Ada ===== Для Ada компилятор порождает 15 дополнительных методов. Два из них - Size() и Alignment() - можно использовать и в C++. Остальные не имеют смысла для интерфейсов и могут быть пустыми. Для получения в C++ класса, совместимого по интерфейсу с Ada, предлагается в качестве базового класса использовать Ada_Compatible: class Ada_Compatible { public: virtual int Size () = 0; virtual int Alignment () = 0; virtual void _TSS_Stream_Read () = 0; virtual void _TSS_Stream_Write () = 0; virtual void *_TSS_Stream_Input () = 0; virtual void _TSS_Stream_Output () = 0; virtual int _Op_Eq () = 0; virtual void _Assign () = 0; virtual void _TSS_Deep_Adjust () = 0; virtual void _TSS_Deep_Finalize () = 0; virtual void _Disp_Asynchronous_Select () = 0; virtual void _Disp_Conditional_Select () = 0; virtual void _Disp_Get_Prim_Op_Kind () = 0; virtual void _Disp_Get_Task_Id () = 0; virtual void _Disp_Timed_Select () = 0; inline void* Tag () { return *(void**) this; } }; #define ADA_COMPATIBLE_IMPLEMENTATION \ virtual int Size () { return sizeof (*this) * 8; } \ virtual int Alignment () { return sizeof (void*); } \ virtual void _TSS_Stream_Read () {} \ virtual void _TSS_Stream_Write () {} \ virtual void *_TSS_Stream_Input () { return 0; } \ virtual void _TSS_Stream_Output () {} \ virtual int _Op_Eq () { return 0; } \ virtual void _Assign () {} \ virtual void _TSS_Deep_Adjust () {} \ virtual void _TSS_Deep_Finalize () {} \ virtual void _Disp_Asynchronous_Select () {} \ virtual void _Disp_Conditional_Select () {} \ virtual void _Disp_Get_Prim_Op_Kind () {} \ virtual void _Disp_Get_Task_Id () {} \ virtual void _Disp_Timed_Select () {} Пример реализации класса Arithmetic с интерфейсом Sequence: #include "ada-compatible.h" class Sequence : public Ada_Compatible { public: virtual int Value () = 0; virtual void Next () = 0; }; class Arithmetic : public Sequence { ADA_COMPATIBLE_IMPLEMENTATION; int val; public: virtual int Value () { return val; } virtual void Next () { val += 1; } }; Интерфейс Sequence соответствует следующему коду на языке Ada: type Sequence is interface; function Value (x: in Sequence) return Integer is abstract; procedure Next (x: in out Sequence) is abstract; ---- ===== Преобразование и сравнение типов ===== Для динамического преобразования и сравнения типов необходимо реализовать дополнительные методы, например: virtual char *Type_Name () = 0; virtual void *Cast (char *type_name) = 0; function Type_Name (x: in Тип) return Interfaces.C.char_array is abstract; function Cast (x: in Тип; type_name: Interfaces.C.char_array) return System.Address is abstract; ----