Альтернатива ссылочной виртуальной таблице в C без использования указателей на функции.
Я использую преимущества полиморфизма в C, используя виртуальные таблицы, как описано в Polymorphism (в C), и он прекрасно работает.
К сожалению, ограничение моего текущего проекта не позволяет мне использовать указатель на функцию или ссылку на структуры в некоторой части моего кода. Как следствие, я не могу использовать оригинальный подход напрямую.
В упомянутом подходе базовый класс / структура имеет член, который указывает на виртуальную таблицу. Чтобы использовать этот указатель, я решил заменить его на перечисление, которое действует как ключ для доступа к виртуальной таблице.
Это работает, но мне интересно, если это лучшее решение. Вы предлагаете какую-нибудь альтернативу, которая подходит лучше, чем мое предложение?
/**
* This example shows a common approach to achive polymorphism in C and an
* alternative that does NOT include a reference to function pointer in the
* base
* class.
**/
#include<stdio.h>
// some functions to make use of polymorphism
void funBase1()
{
printf("base 1 \n");
}
void funBase2()
{
printf("base 2 \n");
}
void funDerived1()
{
printf("derived 1 \n");
}
void funDerived2()
{
printf("derived 2 \n");
}
// struct to host virtual tables
typedef struct vtable {
void (*method1)(void);
void (*method2)(void);
}sVtable;
// enumerate to access the virtual table
typedef enum {BASE, DERIVED} eTypes;
// global virtual table used for the alternative solution
const sVtable g_vtableBaseAlternative[] = {
{funBase1, funBase2},
{funDerived1, funDerived2}, };
// original approach that i cannot use
typedef struct base {
const sVtable* vtable;
int baseAttribute;
}sBase;
// alternative approach
typedef struct baseAlternative {
const eTypes vtable_key;
int baseAttribute;
}sBaseAlternative;
typedef struct derived {
sBase base;
int derivedAttribute;
}sDerived;
// original way to use
static inline void method1(sBase* base)
{
base->vtable->method1();
}
const sVtable* getVtable(const int key, const sVtable* vTableDic)
{
return &vTableDic[key];
}
// Alternative to get a reference to the virtual table
static inline void method1Aternative(sBaseAlternative* baseAlternative)
{
const sVtable* vtable;
vtable = getVtable(baseAlternative->vtable_key, g_vtableBaseAlternative);
printf("alternative version: ");
vtable->method1();
}
int main() {
const sVtable vtableBase[] = { {funBase1, funBase2} };
const sVtable vtableDerived[] = { {funDerived1, funDerived2} };
sBase base = {vtableBase, 0 };
sBase derived = {vtableDerived, 1 };
sBaseAlternative baseAlternative = {DERIVED, 1 };
method1(&base);
method1(&derived);
method1Aternative(&baseAlternative);
}
2 ответа
мой текущий проект не позволяет мне использовать указатель функции или ссылку на структуры
Вы могли бы использовать массив T
(любой тип, который вам нравится) для представления типа данных. Например, я склонен использовать массивы unsigned char
сериализовать и десериализовать мои структуры данных для веб-передачи... Например, предположим, что вы используете sprintf
а также sscanf
для сериализации и десериализации (что на самом деле делать не следует, но для демонстраций это нормально)... Вместо struct
аргументы, вы используете char *
аргументы, а вы используете sscanf
читать эти данные в локальные переменные, sprintf
изменить его... который охватывает отсутствие ссылки на struct
разрешенная проблема.
Что касается проблемы указателя на функцию, вы можете объединить все свои функции в одну, которая switch
es on... теговая структура в строковой форме... Вот простой (но не полный) пример, включающий двух кандидатов в классы: строку с префиксом длины, которая использует два байта для кодирования длины и вид производных от поведения C-строки и строка C.
enum { fubar_is_string, fubar_is_length_prefixed_string };
typedef unsigned char non_struct_str_class;
size_t non_struct_strlen(non_struct_str_class *fubar) {
size_t length = 0;
switch (fubar++[0]) {
case fubar_is_length_prefixed_string:
length = fubar++[0];
length <<= 8;
length += fubar++[0];
// carry through into the next case
// to support strings longer than 64KB
case fubar_is_string: if (!length)
length = strlen(fubar);
/* handle fubar as string */
}
return length;
}
C - полный программируемый язык программирования, поэтому, конечно, его можно использовать для имитации объектно-ориентированного полиморфизма... но он гораздо лучше имитирует процедурный полиморфизм, а в некоторых случаях даже функциональный полиморфизм... В качестве примера можно сказать, qsort
а также bsearch
использовать примитивную форму параметрического полиморфизма, аналогичного map
(еще больше похоже на filter
идиома).
Вы также можете использовать _Generic
с ограниченным успехом, например, как стандарт C11 делает, предоставляя общий cbrt
макрос для всех стандартных типов с плавающей точкой:
#define cbrt(X) _Generic((X), \ long double: cbrtl, \ default: cbrt, \ float: cbrtf \ )(X)
Препроцессор особенно полезен, если вы собираетесь идти по пути мимики... Возможно, вас заинтересует книга Клеменса "С11".
Я пользуюсь полиморфизмом в C
ты не конечно. Вы только создаете протез этого. ИМО симуляция объектов на С - худшее из возможных решений. Если вы предпочитаете парадигму ООП - используйте язык ООП. В этом случае C++.
Отвечая на ваш вопрос - вы не можете сделать это (нормальным способом) без указателей на функции.
Я отговариваю людей от попыток ООП, таких как программирование на процедурных языках. Обычно это приводит к менее читаемым, подверженным ошибкам и очень сложным в обслуживании программам.
Выберите правильный инструмент (язык является инструментом) для задачи и метода.
Это как использовать нож вместо отвертки. Можно, но отвертка точно будет намного лучше.