Объектная ориентация в C - PullRequest
145 голосов
/ 06 января 2009

Каким будет набор изящных препроцессорных хаков (совместимых с ANSI C89 / ISO C90), которые обеспечивают некую некрасивую (но пригодную для использования) объектную ориентацию в C?

Я знаком с несколькими различными объектно-ориентированными языками, поэтому, пожалуйста, не отвечайте с ответами типа "Learn C ++!". Я прочитал « Объектно-ориентированное программирование с ANSI C » (будьте осторожны: Формат PDF ) и несколько других интересных решений, но в основном я заинтересован в вашем: -)!


См. Также Можете ли вы написать объектно-ориентированный код на C?

Ответы [ 21 ]

1 голос
/ 06 января 2009

Если бы я собирался написать ООП на C, я бы, вероятно, пошел с псевдо- Pimpl дизайном. Вместо того, чтобы передавать указатели на структуры, вы в конечном итоге передаете указатели на указатели на структуры. Это делает содержимое непрозрачным и облегчает полиморфизм и наследование.

Настоящая проблема с ООП в C заключается в том, что происходит, когда переменные выходят из области видимости. Деструкторов, генерируемых компилятором, не существует, и это может вызвать проблемы. Макросы могут помочь, но смотреть на это всегда будет уродливо.

1 голос
/ 10 октября 2013

Моя рекомендация: будь проще. Одна из самых больших проблем, с которыми я сталкиваюсь, - это поддержка старого программного обеспечения (иногда старше 10 лет). Если код не простой, это может быть сложно. Да, можно написать очень полезный ООП с полиморфизмом на C, но это может быть трудно читать.

Я предпочитаю простые объекты, которые содержат некоторые четко определенные функции. Отличным примером этого является GLIB2 , например, хеш-таблица:

GHastTable* my_hash = g_hash_table_new(g_str_hash, g_str_equal);
int size = g_hash_table_size(my_hash);
...

g_hash_table_remove(my_hash, some_key);

Ключи:

  1. Простая схема архитектуры и дизайна
  2. Достигает базовой ООП инкапсуляции.
  3. Простота реализации, чтения, понимания и обслуживания
1 голос
/ 17 апреля 2019

Еще один способ программирования в объектно-ориентированном стиле на C - это использовать генератор кода, который преобразует язык, специфичный для предметной области, в C. Как это делается с помощью TypeScript и JavaScript, чтобы перенести OOP в js.

0 голосов
/ 24 июля 2012
#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"

#include <stdio.h>

int main()
{
    Triangle tr1= CTriangle->new();
    Rectangle rc1= CRectangle->new();

    tr1->width= rc1->width= 3.2;
    tr1->height= rc1->height= 4.1;

    CPolygon->printArea((Polygon)tr1);

    printf("\n");

    CPolygon->printArea((Polygon)rc1);
}

Выход:

6.56
13.12

Вот пример того, что такое ОО-программирование на C.

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

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

0 голосов
/ 07 марта 2019

Я также работаю над этим на основе макро решения. Так что это только для самых смелых, я думаю ;-) Но это уже довольно приятно, и я уже работаю над несколькими проектами в дополнение к этому. Это работает так, что вы сначала определяете отдельный заголовочный файл для каждого класса. Как это:

#define CLASS Point
#define BUILD_JSON

#define Point__define                            \
    METHOD(Point,public,int,move_up,(int steps)) \
    METHOD(Point,public,void,draw)               \
                                                 \
    VAR(read,int,x,JSON(json_int))               \
    VAR(read,int,y,JSON(json_int))               \

Чтобы реализовать класс, вы создаете для него заголовочный файл и файл C, в котором вы реализуете методы:

METHOD(Point,public,void,draw)
{
    printf("point at %d,%d\n", self->x, self->y);
}

В заголовок, который вы создали для класса, вы включаете другие нужные вам заголовки и определяете типы и т. Д., Относящиеся к классу. И в заголовке класса, и в файле C вы включаете файл спецификации класса (см. Первый пример кода) и X-макрос. Эти X-макросы ( 1 , 2 , 3 и т. Д.) Расширяют код до фактических структур классов и других объявлений.

Чтобы наследовать класс, #define SUPER supername и добавьте supername__define \ в качестве первой строки в определении класса. Оба должны быть там. Также есть поддержка JSON, сигналы, абстрактные классы и т. Д.

Чтобы создать объект, просто используйте W_NEW(classname, .x=1, .y=2,...). Инициализация основана на инициализации структуры, представленной в C11. Это работает хорошо, и все, что не перечислено, установлено в ноль.

Чтобы вызвать метод, используйте W_CALL(o,method)(1,2,3). Это похоже на вызов функции более высокого порядка, но это просто макрос. Он расширяется до ((o)->klass->method(o,1,2,3)), что является действительно хорошим хаком.

См. Документация и сам код .

Так как фреймворку нужен некоторый шаблонный код, я написал Perl-скрипт (wobject), который выполняет эту работу. Если вы используете это, вы можете просто написать

class Point
    public int move_up(int steps)
    public void draw()
    read int x
    read int y

и он создаст файл спецификации класса, заголовок класса и файл C, который включает Point_impl.c, где вы реализуете класс. Это экономит много работы, если у вас много простых классов, но все еще в C. wobject - очень простой сканер на основе регулярных выражений, который легко адаптировать к конкретным потребностям или переписать с царапина.

0 голосов
/ 28 марта 2019

Проект с открытым исходным кодом Dynace делает именно это. Это на https://github.com/blakemcbride/Dynace

0 голосов
/ 26 августа 2017

Если вам нужно написать немного кода попробуйте это: https://github.com/fulminati/class-framework

#include "class-framework.h"

CLASS (People) {
    int age;
};

int main()
{
    People *p = NEW (People);

    p->age = 10;

    printf("%d\n", p->age);
}
0 голосов
/ 30 мая 2017

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

/*
 * OOP in C
 *
 * gcc -o oop oop.c
 */

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

struct obj2d {
    float x;                            // object center x
    float y;                            // object center y
    float (* area)(void *);
};

#define X(obj)          (obj)->b1.x
#define Y(obj)          (obj)->b1.y
#define AREA(obj)       (obj)->b1.area(obj)

void *
_new_obj2d(int size, void * areafn)
{
    struct obj2d * x = calloc(1, size);
    x->area = areafn;
    // obj2d constructor code ...
    return x;
}

// --------------------------------------------------------

struct rectangle {
    struct obj2d b1;        // base class
    float width;
    float height;
    float rotation;
};

#define WIDTH(obj)      (obj)->width
#define HEIGHT(obj)     (obj)->height

float rectangle_area(struct rectangle * self)
{
    return self->width * self->height;
}

#define NEW_rectangle()  _new_obj2d(sizeof(struct rectangle), rectangle_area)

// --------------------------------------------------------

struct triangle {
    struct obj2d b1;
    // deliberately unfinished to test error messages
};

#define NEW_triangle()  _new_obj2d(sizeof(struct triangle), triangle_area)

// --------------------------------------------------------

struct circle {
    struct obj2d b1;
    float radius;
};

#define RADIUS(obj)     (obj)->radius

float circle_area(struct circle * self)
{
    return M_PI * self->radius * self->radius;
}

#define NEW_circle()     _new_obj2d(sizeof(struct circle), circle_area)

// --------------------------------------------------------

#define NEW(objname)            (struct objname *) NEW_##objname()


int
main(int ac, char * av[])
{
    struct rectangle * obj1 = NEW(rectangle);
    struct circle    * obj2 = NEW(circle);

    X(obj1) = 1;
    Y(obj1) = 1;

    // your decision as to which of these is clearer, but note above that
    // macros also hide the fact that a member is in the base class

    WIDTH(obj1)  = 2;
    obj1->height = 3;

    printf("obj1 position (%f,%f) area %f\n", X(obj1), Y(obj1), AREA(obj1));

    X(obj2) = 10;
    Y(obj2) = 10;
    RADIUS(obj2) = 1.5;
    printf("obj2 position (%f,%f) area %f\n", X(obj2), Y(obj2), AREA(obj2));

    // WIDTH(obj2)  = 2;                                // error: struct circle has no member named width
    // struct triangle  * obj3 = NEW(triangle);         // error: triangle_area undefined
}

Я думаю, что это имеет хороший баланс, и ошибки, которые он генерирует (по крайней мере, с опциями gcc 6.3 по умолчанию) для некоторых из наиболее вероятных ошибок, полезны, а не сбивают с толку. Все дело в том, чтобы повысить производительность труда программиста нет?

0 голосов
/ 08 августа 2016

Посмотрите на http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html. Если ничто иное, как чтение документации, не является просветляющим опытом.

0 голосов
/ 06 января 2009

Для меня объектная ориентация в Си должна иметь следующие особенности:

  1. Инкапсуляция и сокрытие данных (может быть достигнуто с помощью структур / непрозрачных указателей)

  2. Наследование и поддержка полиморфизма (одиночное наследование может быть достигнуто с помощью структур - убедитесь, что абстрактная база не является экземпляром)

  3. Функциональность конструктора и деструктора (нелегко достичь)

  4. Проверка типов (по крайней мере, для пользовательских типов, так как C не применяет их)

  5. Подсчет ссылок (или что-то для реализации RAII )

  6. Ограниченная поддержка обработки исключений (setjmp и longjmp)

Помимо вышесказанного, он должен опираться на спецификации ANSI / ISO и не должен полагаться на специфичные для компилятора функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...