[ <<< | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Источники | >>> ]
© 2001 А.И. Легалов
Использование процедурного подхода не позволяет явно "запихнуть" методы в агрегаты или обобщения. Однако практически любой процедурный язык, имеющий развитые абстрактные типы данных, позволяет легко сымитировать объектное обобщение. Именно этот подход используется при трансляции объектно-ориентированных программ в машинный код. Здесь стоит вспомнить компилятор cfront, разработанный Страуструпом [Страуструп2000]. Пример кода, порождаемого подобным компилятором можно подсмотреть в книге Голуба [Голуб]. Подобный прием наверняка использовался многими программистами еще до наступления эры ООП с целью создания более гибкой программы. Правда, я не согласен с высказыванием Страуструпа, что компилятор в данном случае порождает более эффективный код, чем тот, который может написать программист. Как и в случае программирования на ассемблере, имитация ОО стиля в процедурном подходе позволяет написать более эффективную программу, если учитывать при этом специфику решаемой задачи. Но также нет сомнений, что эта эффективность будет достигаться с большими затратами. А отсутствие контроля во время компиляции ведет к тому, что многие ошибки будут выявлены только при выполнении программы.
Почему-то я решил, что мои заметки не будут полными без простенькой процедурной программки в ОО стиле. Поэтому, не уделяя внимания оптимизации, я привожу свой вариант для рассматриваемого примера. Надеюсь, что программа наглядно демонстрирует те затраты, от которых удалось избавиться, перейдя на объектно-ориентированные языки. Небольшой нюанс проявляется в том, что я попытался по максимуму использовать структуры данных, написанные ранее для вариантного обобщения. Таблица виртуальных функций базового класса shape в данном случае выделена в отдельную структуру, что позволяет на ее основе генерировать конкретные таблицы виртуальных функций для вновь создаваемых специализаций. Код моделируемого объектного обобщения выглядит следующим образом:
//---------------------------------------------------------------- // Структура таблицы виртуальных функций базоваого класса // Содержит указатели на функции, переопределяемые // в моделях производных классов. struct shape_vtbl { void (*In)(shape *_this); // ввод данных из стандартного потока void (*Out)(shape *_this); // вывод данных в стандартный поток double (*Area)(shape *_this); // вычисление площади фигуры void (*Destroy)(shape *_this); // удаление обобщенной фигуры }; //---------------------------------------------------------------- // Структура, обобщающая все имеющиеся фигуры. // Моделирует абстрактный базовый класс. struct shape { shape_vtbl *vtbl; // Указатель на таблицу виртуальных функций }; //---------------------------------------------------------------- |
Имитируя наследование, можно построить прямоугольник и треугольник.
//---------------------------------------------------------------- // Функции, инициализирующие таблицу прямоугольника void In_shape_rectangle(shape* _this); // ввод данных void Out_shape_rectangle(shape* _this); // вывод данных double Area_shape_rectangle(shape* _this); // вычисление площади void Destroy_shape_rectangle(shape* _this); // удаление фигуры //---------------------------------------------------------------- // Таблица виртуальных функций прямоугольника. // Задается одна для всех прямоугольников. Спрятана в файле // Инициализируется специально сформированными расширяющими функциями static shape_vtbl rect_vtbl = { &In_shape_rectangle, // ввод данных из стандартного потока &Out_shape_rectangle, // вывод данных в стандартный поток &Area_shape_rectangle, // вычисление площади фигуры &Destroy_shape_rectangle // удаление динамически созданной фигуры }; //---------------------------------------------------------------- // Структура, моделирующая прямоугольник - наследник фигуры. struct shape_rectangle { shape base; // База специализированной фигуры rectangle r; // Значимая часть. Может быть написана независимо. }; //---------------------------------------------------------------- // Функции, инициализирующие таблицу треугольника void In_shape_triangle(shape* _this); // ввод данных void Out_shape_triangle(shape* _this); // вывод данных double Area_shape_triangle(shape* _this); // вычисление площади фигуры void Destroy_shape_triangle(shape* _this); // удаление фигуры //---------------------------------------------------------------- // Таблица виртуальных функций треугольника. // Задается одна для всех треугольников. Спрятана в файле // Инициализируется специально сформированными расширяющими функциями static shape_vtbl trian_vtbl = { &In_shape_triangle, // ввод данных из стандартного потока &Out_shape_triangle, // вывод данных в стандартный поток &Area_shape_triangle, // вычисление площади фигуры &Destroy_shape_triangle // удаление динамически созданной фигуры }; //---------------------------------------------------------------- // Структура, моделирующая треугольник - наследник фигуры. struct shape_triangle { shape base; // База специализированной фигуры triangle t; // Значимая часть. Может быть написана независимо. }; //---------------------------------------------------------------- |
Таблицы виртуальных функций имитируются посредством соответствующих статических переменных. Это приводит к тому, что существует несколько одинаковых таблиц размещенных в разных единицах компиляции. Но я специально не стал вводить одну глобальную переменную, чтобы не думать о том, в каком файле ее определить, а в каких - объявить. Кстати, такой прием может использоваться и компилятором [Страуструп2000].
В методах фигур-наследников используется явное приведение обобщающего типа к типу специализаций. После этого осуществляется доступ к структуре, определяющей конкретную фигуру и обработка ее данных ранее написанными методами. Техника достаточно проста, а исходные тексты всего примера размещены в архиве pp_examp1o.zip.
Следует отметить, что окончательная структура объекта, имитирующего класс, формируется только во время выполнения программы. Это связано с тем, что таблица виртуальных функций связывается через указатель, значение которому может быть присвоено только соответствующим кодом. Этот код размещается в процедурах создания и инициализации структур, определяющих производные типы данных. Вот как они выглядят:
//---------------------------------------------------------------- void Init(rectangle &r, int x, int y); // Динамическое создание прямоугольника - наследника по двум сторонам shape_rectangle *Create_shape_rectangle(int x, int y) { shape_rectangle *sr = new shape_rectangle; // Привязка к виртуальной таблице sr->base.vtbl = &rect_vtbl; Init(sr->r, x, y); return sr; } // Инициализация прямоугольника - наследника по двум сторонам void Init_shape_rectangle(shape_rectangle &sr, int x, int y) { // Привязка к виртуальной таблице sr.base.vtbl = &rect_vtbl; Init(sr.r, x, y); } //---------------------------------------------------------------- void Init(triangle &t, int a, int b, int c); // Динамическое создание треугольника - наследника по трем сторонам shape_triangle *Create_shape_triangle(int a, int b, int c) { shape_triangle *st = new shape_triangle; // Привязка к виртуальной таблице st->base.vtbl = &trian_vtbl; Init(st->t, a, b, c); return st; } // Инициализация треугольника - наследника по трем сторонам void Init_shape_triangle(shape_triangle &st, int a, int b, int c) { // Привязка к виртуальной таблице st.base.vtbl = &trian_vtbl; Init(st.t, a, b, c); } //---------------------------------------------------------------- |
Наличие такой динамической привязки к таблице указателей на функции объясняет, почему классы должны иметь процедуры по умолчанию.
Кстати, можно обойтись и без внешней таблицы виртуальных функций, разместив указатели на сменяемые функции внутри структуры, имитирующей базовый класс, который будет выглядеть следующим образом:
//---------------------------------------------------------------- // Структура, обобщающая все имеющиеся фигуры. // Моделирует абстрактный базовый класс. struct shape { // Непосредственное размещение указателей на заменямые функции // вместо таблицы виртуальных функций void (*In)(shape *_this); // ввод данных void (*Out)(shape *_this); // вывод данных double (*Area)(shape *_this); // вычисление площади фигуры void (*Destroy)(shape *_this); // удаление обобщенной фигуры }; //---------------------------------------------------------------- |
В этом случае можно обойтись без статических или глобальных переменных, определяющих таблицы виртуальных функций для специализаций обобщения:
//---------------------------------------------------------------- // Структура, моделирующая прямоугольник - наследник фигуры. struct shape_rectangle { shape base; // База специализированной фигуры rectangle r; // Значимая часть. Может быть написана независимо. }; //---------------------------------------------------------------- // Структура, моделирующая треугольник - наследник фигуры. struct shape_triangle { shape base; // База специализированной фигуры triangle t; // Значимая часть. Может быть написана независимо. }; //---------------------------------------------------------------- |
Но в этом случае формирование связей с обработчиками специализаций придется осуществлять во время инициализации. То есть, модель конструктора производного объекта станет более громоздкой. Вот как они могут выглядеть для рассмотренных структур:
//---------------------------------------------------------------- void Init(rectangle &r, int x, int y); // Функции, инициализирующие таблицу прямоугольника void In_shape_rectangle(shape* _this); // ввод данных void Out_shape_rectangle(shape* _this); // вывод данных double Area_shape_rectangle(shape* _this); // вычисление площади фигуры void Destroy_shape_rectangle(shape* _this); // удаление фигуры // Динамическое создание прямоугольника - наследника по двум сторонам shape_rectangle *Create_shape_rectangle(int x, int y) { shape_rectangle *sr = new shape_rectangle; sr->base.In = &In_shape_rectangle; sr->base.Out = &Out_shape_rectangle; sr->base.Area = &Area_shape_rectangle; sr->base.Destroy = &Destroy_shape_rectangle; Init(sr->r, x, y); return sr; } // Инициализация прямоугольника - наследника по двум сторонам void Init_shape_rectangle(shape_rectangle &sr, int x, int y) { sr.base.In = &In_shape_rectangle; sr.base.Out = &Out_shape_rectangle; sr.base.Area = &Area_shape_rectangle; sr.base.Destroy = &Destroy_shape_rectangle; Init(sr.r, x, y); } //---------------------------------------------------------------- void Init(triangle &t, int a, int b, int c); // Функции, инициализирующие таблицу треугольника void In_shape_triangle(shape* _this); // ввод данных void Out_shape_triangle(shape* _this); // вывод данных double Area_shape_triangle(shape* _this); // вычисление площади фигуры void Destroy_shape_triangle(shape* _this); // удаление фигуры // Динамическое создание треугольника - наследника по трем сторонам shape_triangle *Create_shape_triangle(int a, int b, int c) { shape_triangle *st = new shape_triangle; st->base.In = &In_shape_triangle; st->base.Out = &Out_shape_triangle; st->base.Area = &Area_shape_triangle; st->base.Destroy = &Destroy_shape_triangle; Init(st->t, a, b, c); return st; } // Инициализация треугольника - наследника по трем сторонам void Init_shape_triangle(shape_triangle &st, int a, int b, int c) { st.base.In = &In_shape_triangle; st.base.Out = &Out_shape_triangle; st.base.Area = &Area_shape_triangle; st.base.Destroy = &Destroy_shape_triangle; Init(st.t, a, b, c); } //---------------------------------------------------------------- |
Этот подход позволяет ускорить выполнение программы, так как отсутствует дополнительное обращение через ссылку к таблице виртуальных функций. Однако эффект достигается за счет расточительного использования памяти, так как каждая структура должна содержать все указатели. Кроме того, усложняются и становятся более медленными функции, выполняющие конструирование, так как теперь необходимо осуществлять привязку обработчиков специализаций для каждого создаваемого объекта, имитирующего производный класс. К сожалению, здесь нельзя воспользоваться статическими полями структуры. Исходные тексты примера размещены в архиве pp_examp1od.zip.
[ <<< | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Источники | >>> ]