====== Реализация интерфейсов в 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;
----