Альтернатива ссылочной виртуальной таблице в 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++.

Отвечая на ваш вопрос - вы не можете сделать это (нормальным способом) без указателей на функции.

Я отговариваю людей от попыток ООП, таких как программирование на процедурных языках. Обычно это приводит к менее читаемым, подверженным ошибкам и очень сложным в обслуживании программам.

Выберите правильный инструмент (язык является инструментом) для задачи и метода.

Это как использовать нож вместо отвертки. Можно, но отвертка точно будет намного лучше.

Другие вопросы по тегам