Подтвердить что ты не робот

Как смоделировать полиморфизм OO-стиля в C?

Есть ли способ написать OO-подобный код на языке программирования C?


См. также:

Найден поиск по "[c] oo".

4b9b3361

Ответ 1

Первый компилятор С++ ( "C с классами" ) фактически сгенерирует C-код, поэтому это возможно.

В принципе, ваш базовый класс является структурой; производные структуры должны включать базовую структуру в первой позиции, так что указатель на "производную" структуру также будет действительным указателем на базовую структуру.

typedef struct {
   data member_x;
} base;

typedef struct {
   struct base;
   data member_y;
} derived;

void function_on_base(struct base * a); // here I can pass both pointers to derived and to base

void function_on_derived(struct derived * b); // here I must pass a pointer to the derived class

Функции могут быть частью структуры в качестве указателей функций, так что возможен синтаксис, такой как p- > call (p), но вы все равно должны явно передать указатель на структуру самой функции.

Ответ 2

Общий подход заключается в определении структуры с указателями на функции. Это определяет "методы", которые можно вызывать на любом типе. Затем подтипы устанавливают свои собственные функции в этой общей структуре и возвращают ее.

Например, в ядре linux существует struct:

struct inode_operations {
    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
    struct dentry * (*lookup) (struct inode *,struct dentry *, 
                               struct nameidata *);
    ...
};

Каждый зарегистрированный тип файловой системы регистрирует свои собственные функции для create, lookup и остальных функций. Остальная часть кода может использовать общие операции inode_operations:

struct inode_operations   *i_op;
i_op -> create(...);

Ответ 3

С++ не так далеко от C.

Классы - это структуры со скрытым указателем на таблицу указателей функций, называемых VTable. Сам Vtable является статическим. Когда типы указывают на Vtables с одинаковой структурой, но где указатели указывают на другую реализацию, вы получаете полиморфизм.

Рекомендуется инкапсулировать логику вызовов в функцию, которая принимает структуру как параметр, чтобы избежать помех кода.

Вы также должны инкапсулировать структуру экземпляров и инициализацию в функциях (это эквивалентно конструктору С++) и удалению (деструктор в С++). В любом случае, это хорошая практика.

typedef struct
{
   int (*SomeFunction)(TheClass* this, int i);
   void (*OtherFunction)(TheClass* this, char* c);
} VTable;

typedef struct
{
   VTable* pVTable;
   int member;

} TheClass;

Чтобы вызвать метод:

int CallSomeFunction(TheClass* this, int i)
{
  (this->pVTable->SomeFunction)(this, i);
}

Ответ 4

Я посмотрел на все ответы elses и придумал следующее:

#include <stdio.h>

typedef struct
{
    int (*get)(void* this);
    void (*set)(void* this, int i);
    int member;

} TheClass;

int Get(void* this)
{
    TheClass* This = (TheClass*)this;
    return This->member;
}

void Set(void* this, int i)
{
    TheClass* This = (TheClass*)this;
    This->member = i;
}

void init(TheClass* this)
{
    this->get = &Get;
    this->set = &Set;
}

int main(int argc, char **argv)
{
    TheClass name;
    init(&name);
    (name.set)(&name, 10);
    printf("%d\n", (name.get)(&name));
    return 0;
}

Надеюсь, что ответы на некоторые вопросы.

Ответ 5

Приложение B статьи Открытые многоразовые объектные модели, Ян Пимарта и Алессандро Варт VPRI - это реализация объектной модели в GNU C, около 140 строк кода. Это захватывающее прочтение!

Здесь нераскрытая версия макроса, который отправляет сообщения объектам, используя расширение GNU для выражения C (выражение оператора):

struct object;

typedef struct object *oop; 
typedef oop *(*method_t)(oop receiver, ...);

//...

#define send(RCV, MSG, ARGS...) ({ \ 
    oop r = (oop)(RCV); \ 
    method_t method = _bind(r, (MSG)); \ 
    method(r, ##ARGS); \ 
}) 

В том же документе рассмотрим структуры object, vtable, vtable_delegated и symbol и функции _bind и vtable_lookup.

Ура!

Ответ 6

Файловые функции fopen, fclose, fread являются примерами OO-кода на C. Вместо закрытых данных в классе они работают со структурой FILE, которая используется для инкапсуляции данных, а функции C действуют как функции класса-члена. http://www.amazon.com/File-Structures-Object-Oriented-Approach-C/dp/0201874016

Ответ 7

#include <stdio.h>

typedef struct {
    int  x;
    int z;
} base;

typedef struct {
    base;
    int y;
    int x;
} derived;

void function_on_base( base * a) // here I can pass both pointers to derived and to base
{
    printf("Class base [%d]\n",a->x);
    printf("Class base [%d]\n",a->z);
}
void function_on_derived( derived * b) // here I must pass a pointer to the derived class
{
    printf("Class derived [%d]\n",b->y);
    printf("Class derived [%d]\n",b->x);
}

int main()
{
    derived d;
    base b;
    printf("Teste de poliformismo\n");

    b.x = 2;
    d.y = 1;
    b.z = 3;
    d.x = 4;
    function_on_base(&b);
    function_on_base(&d);
    function_on_derived(&b);
    function_on_derived(&d);
    return 0;
}

Выход был:

Class base [3]
Class base [1]
Class base [4]
Class derived [2]
Class derived [3]
Class derived [1]
Class derived [4]

поэтому он работает, его полиморфный код.

UncleZeiv объяснил это в начале.

Ответ 8

Из Википедии: В языках программирования и теории типов полиморфизм (от греческого πολύς, polys, "many, much" и μορφή, morphē, "form, shape" ) - это предоставление единого интерфейса объектам разных типов.

Итак, я бы сказал, что единственный способ реализовать его в C - это использование вариативных аргументов наряду с некоторым (полу) автоматическим типом управления информацией. Например, в С++ вы можете написать (извините за тривиальность):

void add( int& result, int a1, int a2 );
void add( float& result, float a1, float a2 );
void add( double& result, double a1, double a2 );

В C, среди других решений, лучшее, что вы можете сделать, это что-то вроде этого:

int int_add( int a1, int a2 );
float float_add( float a1, fload a2 );
double double_add( double a1, double a2 );

void add( int typeinfo, void* result, ... );

Тогда вам нужно:

  • для реализации "typeinfo" с перечислениями/макросами
  • для реализации последней функции с помощью файла stdarg.h
  • попрощаться с проверкой статического типа C

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

Ответ 9

Для того, чтобы слишком быстро создавать функциональность OO на C, вы можете посмотреть предыдущие ответы.

Но (как это было задано в других вопросах, перенаправленных на этот), если вы хотите понять, что такое полиморфизм, на примерах на языке C. Возможно, я ошибаюсь, но я не могу думать ни о чем столь же легко понять, как арифметика C-указателей. На мой взгляд, указательная арифметика по своей сути полиморфна в C. В следующем примере одна и та же функция (метод в OO), а именно добавление (+), приведет к другому поведению в зависимости от свойств входных структур.

Пример:

double a*;
char str*;

a=(double*)malloc(2*sizeof(double));
str=(char*)malloc(2*sizeof(char)); 

a=a+2; // make the pointer a, point 2*8 bytes ahead.

str=str+2; // make the pointer str, point 2*1 bytes ahead.

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

Ответ 10

Существующие ответы находятся на правильном пути. Но ни одно из них не является полным решением.

Полное решение включает в себя следующее:

  • Это безопасно за пределами библиотеки: вы подкласса базового класса, который находится в библиотеке в вашем коде (возможно, с вашим собственным распределителем памяти).
  • Демонстрирует полиморфизм действительно работает.
  • Предоставляет способы для безопасных downcast.

Полиморфизм обычно выполняется таблицей виртуальных методов, которая связана во время выполнения. Вот публичный заголовочный файл, который определяет базовый класс универсального объекта:

#pragma once

// Put virtual functions into your own a virtual method table.
typedef struct
{    
    // Creates another handle to the object.
    void* (*addref)(void *pThis);
    // Releases handle to object
    void (*release)(void *pThis);
    // Returns string representation of the object type
    const char* (*getType)(void *pThis);
    // Returns non-zero if the current object has the given dynamic type.
    int (*isA)(void *pThis, const char *typeName);
    // Cleans up the object. Implementers must call be base class destructor too for proper cleanup.
    void (*destructor)(void *pThis);
    // Function used to clean up the memory of the object.
    void (*deallocate)(void *pThis);
} Object_vtable;

typedef struct
{
    void *vtable;
    int refCount;
} Object;

extern Object_vtable object_vtable;

// In C++ you can't directly call this (except placement new).
void Object_constructor(Object *pThis);

// PUBLIC INTERFACE

// Creates new object on heap.
Object *new_Object();

// Creates another handle to the current object.
Object* Object_Addref(Object *pThis);

// Returns string representation of the object type
const char* Object_GetType(Object *pThis);

// Releases handle to object
void Object_Release(Object *pThis);

// Returns the object itself if the type tag matches what returned by getType.
void* DynamicCast(Object *pThis, const char *typeTag);

Потребители библиотеки должны вызывать только те функции, которые находятся ниже комментария "PUBLIC interface". Субклассерам понадобится отдых.

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

Он также объявляет функцию DynamicCast для проверки, является ли объект заданным динамическим типом.

Теперь давайте посмотрим на реализацию библиотеки:

#include "object.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// "custom" allocator

static void *myAlloc(size_t x)
{
    printf("Object allocator allocates %d bytes.\n", (int)x);

    return malloc(x);
}

static void myFree(void *x)
{
    printf("Object allocator frees %p\n", x);

    free(x);
}

// Implement interface.

void Object_constructor(Object *pThis)
{
    pThis->vtable = &object_vtable; // Bind VMT now.
    pThis->refCount = 1;
}


Object *new_Object()
{
    Object *newOne = myAlloc(sizeof(*newOne));

    Object_constructor(newOne);

    return newOne;
}


Object* Object_Addref(Object *pThis)
{
    Object_vtable *vtab = pThis->vtable;

    return vtab->addref(pThis);
}


const char* Object_GetType(Object *pThis)
{
    Object_vtable *vtab = pThis->vtable;

    return vtab->getType(pThis);
}


void Object_Release(Object *pThis)
{
    Object_vtable *vtab = pThis->vtable;

    return vtab->release(pThis);
}

void* DynamicCast(Object *pThis, const char *typeTag)
{
    Object_vtable *vtab = pThis->vtable;

    return vtab->isA(pThis, typeTag) ? pThis : NULL;
}

// Implement private functions

static void Object_Destroy(Object *pThis)
{
    Object_vtable *vtab = pThis->vtable;

    return vtab->destructor(pThis);
}


static void Object_Deallocate(Object *pThis)
{
    Object_vtable *vtab = pThis->vtable;

    return vtab->deallocate(pThis);
}

// VMT implementation

// Creates another handle to the object.
void *Object_vmt_addref(void *pThis)
{
    Object *o = pThis;
    o->refCount++;

    return o;
}

// Releases handle to object
static void Object_vmt_release(void *pThis)
{
    Object *o = pThis;

    if (--o->refCount == 0)
    {
        Object_Destroy(o);
        Object_Deallocate(o);
    }
}

// Returns string representation of the object type
static const char *Object_vmt_getType(void *pThis)
{
    (void)pThis;
    return "Object";
}

int Object_vmt_isA(void *pThis, const char *typeName)
{
    (void)pThis;

    return !strcmp(typeName, "Object");
}

// Cleans up the object. Implementers must call be base class destructor too for proper cleanup.
static void Object_vmt_destructor(void *pThis)
{
    (void)pThis;
    // Nothing to release.
    printf("Object destructor runs.\n");
}

Object_vtable object_vtable = 
{
    Object_vmt_addref,
    Object_vmt_release,
    Object_vmt_getType,
    Object_vmt_isA,
    Object_vmt_destructor,
    myFree
};

Библиотека использует специальный распределитель для имитации несовместимости ЭЛТ. Вы можете просто использовать malloc и free и скомпилировать его с помощью MSVC, а затем скомпилировать производный класс в MinGW и посмотреть, как они работают вместе.

Следует отметить, что сами реализации виртуальных функций не выставляются из библиотеки.

Реализация упаковщиков виртуальных методов и new_ * довольно проста и повторяема, при желании ее можно скрыть под макросами, но я не сделал этого для ясности.

Теперь пользователь этой библиотеки хочет создать подкласс Object с помощью другого класса, который переопределяет несколько виртуальных методов и добавляет новые. Вот заголовок этого:

#pragma once

#include "object.h"

typedef struct _Derived
{
    Object object;
    char *extraResource;
} Derived;

// Extend the virtual method table with more functions.
typedef struct
{
    Object_vtable objectVtable;
    void (*doFoo)(void *pThis);
} Derived_vtable;

extern Derived_vtable derived_vtable;

void Derived_constructor(Derived *pThis, const char *str);

// PUBLIC interface.

Derived *new_Derived(const char *str);

void Derived_DoFoo(Derived *pThis);

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

Здесь не так много вещей. Только вещи производные. Вот реализация:

#include "derived.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// My allocator

static void* myAlloc(size_t x)
{
    printf("Derived allocator allocates: %d\n", (int)x);

    return malloc(x);
}

static void myFree(void *p)
{
    printf("Derived allocator frees %p\n", p);

    return free(p);
}

// Implement the public interface

static void initVmt(Derived *pThis);

void Derived_constructor(Derived *pThis, const char *str)
{
    // Initialize base first
    Object_constructor((Object*)pThis);
    // Now this class
    initVmt(pThis);
    pThis->extraResource = strdup(str);
}


Derived *new_Derived(const char *str)
{
    Derived *newOne = myAlloc(sizeof(*newOne));

    Derived_constructor(newOne, str);

    return newOne;
}


void Derived_DoFoo(Derived *pThis)
{
    Derived_vtable *vtab = pThis->object.vtable;

    vtab->doFoo(pThis);
}

// Implement VMT

static const char* Derived_vmt_getType(void *pThis)
{
    (void)pThis;
    return "Derived";
}


static int Derived_vmt_isA(void *pThis, const char *typeName)
{
    (void)pThis;

    return !strcmp(typeName, "Derived") || !strcmp(typeName, "Object");
}


static void Derived_vmt_destructor(void *pThis)
{
    Derived *d = pThis;

    printf("In Derived destructor\n");

    free(d->extraResource);

    // Call the destructor of base
    object_vtable.destructor((Object*)pThis);
}


static void Derived_vmt_doFoo(void *pThis)
{
    Derived *d = pThis;

    printf("Doing foo with extra data: %s\n", d->extraResource);
}

static void initVmt(Derived *pThis)
{
    static int alreadyInited = 0;
    if (!alreadyInited)
    {
        // Since it contains addresses the compiler cannot resolve compile time, this initialization should be done runtime.
        // Almost same as the Object's
        derived_vtable.objectVtable = object_vtable;
        // Except these overrides...
        derived_vtable.objectVtable.getType = Derived_vmt_getType;
        derived_vtable.objectVtable.isA = Derived_vmt_isA;
        derived_vtable.objectVtable.destructor = Derived_vmt_destructor;
        derived_vtable.objectVtable.deallocate = myFree;
        // And our stuff.
        derived_vtable.doFoo = Derived_vmt_doFoo;

        alreadyInited = 1;
    }
    pThis->object.vtable = &derived_vtable;
}

Derived_vtable derived_vtable;

И, наконец, тестовая подвеска:

#include "derived.h"

#include <stdio.h>

int main()
{
    // Try out raw objects.

    Object *o = new_Object();
    printf("o type is: %s\n", Object_GetType(o));
    if (DynamicCast(o, "Object"))
    {
        printf("o is an object\n");
    }
    if (DynamicCast(o, "Derived"))
    {
        printf("o is an Derived\n");
    }

    // Exercise its ref buttons a bit.
    Object_Addref(o);
    Object_Addref(o);
    Object_Release(o);
    Object_Release(o);
    Object_Release(o); // Destroyed here.

    // Now the same with derived.
    o = (Object*)new_Derived("Hello world!"); // Behold the polymorphism!
    printf("o type is: %s\n", Object_GetType(o));
    {
        Derived *d = DynamicCast(o, "Derived"); 
        if (d)
        {
            printf("It is Derived! Check if it can do the Foo!\n");
            Derived_DoFoo(d); // Check if it can do foo.
        }

        Object *o2 = DynamicCast(o, "Object");
        if (o2)
        {
            printf("It an object too.\n");
        }


        if (!DynamicCast(o, "Notexist"))
        {
            printf("This dynamic cast fails.\n");
        }
    }
    Object_Release(o); // Destroyed here and deallocated with our allocator.

    return 0;
}

Какие отпечатки:

Object allocator allocates 16 bytes.
o type is: Object
o is an object
Object destructor runs.
Object allocator frees 0x5430480
Derived allocator allocates: 24
o type is: Derived
It is Derived! Check if it can do the Foo!
Doing foo with extra data: Hello world!
It an object too.
This dynamic cast fails.
In Derived destructor
Object destructor runs.
Derived allocator frees 0x54304d0

Следует отметить, что большая часть этого уже изобретена и называется COM.