Какие методы/стратегии используют люди для создания объектов в C (а не С++)?

Мне особенно интересны объекты, предназначенные для использования внутри C, в отличие от реализаций объектов, которые составляют ядро ​​интерпретируемых языков, таких как python.

Ответ 1

Я обычно делаю что-то вроде этого:

struct foo_ops {
    void (*blah)(struct foo *, ...);
    void (*plugh)(struct foo *, ...);
};
struct foo {
    struct foo_ops *ops;
    /* data fields for foo go here */
};

С этими определениями структуры код, реализующий foo, выглядит примерно так:

static void plugh(struct foo *, ...) { ... }
static void blah(struct foo *, ...) { ... }

static struct foo_ops foo_ops = { blah, plugh };

struct foo *new_foo(...) {
   struct foo *foop = malloc(sizeof(*foop));
   foop->ops = &foo_ops;
   /* fill in rest of *foop */
   return foop;
} 

Затем в коде, который использует foo:

struct foo *foop = new_foo(...);
foop->ops->blah(foop, ...);
foop->ops->plugh(foop, ...);

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

foo_blah(foop, ...);
foo_plugh(foop, ...);

хотя, если вы придерживаетесь достаточно короткого имени для поля "ops", просто выписывая первоначально показанный код не является особенно подробным.

Этот метод полностью подходит для реализации относительно простых объектных проектов в C, но он не обрабатывает более сложные требования, такие как явно представляющие классы, и наследование методов. Для них вам может понадобиться что-то вроде GObject (как упоминал EFraim), но я бы посоветовал убедиться, что вам действительно нужны дополнительные функции более сложных фреймворков.

Ответ 2

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

Метод Полиморфизм:

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

struct image_scaler {
    //member variables
    int (*scale)(int, int, int*);
}

Затем я мог бы сделать несколько масштабировщиков изображений как таковых:

struct image_scaler nn, bilinear;
nn->scale = &nearest_neighbor_scale;
bilinear->scale = &bilinear_scale;

Это позволяет мне добиться полиморфного поведения для любой функции, которая принимает в image_scaler и использует его метод масштабирования, просто передавая ему другой image_scaler.

Наследование

Наследование обычно достигается как таковое:

struct base{
   int x;
   int y;
} 

struct derived{
   struct base;
   int z;
}

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

Ответ 3

Библиотеки, такие как GObject.

В основном GObject предоставляет общий способ описания непрозрачных значений (целые числа, строки) и объекты (путем ручного описания интерфейса) как структуры указателей функций, в основном соответствующих VTable на С++). Более подробную информацию о структуре можно найти в ссылка

Вы часто также выполняете перенос vtables вручную, как в "COM в простой C"

Ответ 4

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

Однако я обнаружил, что ОЧЕНЬ важный аспект для программного обеспечения читаемость. Вы пытались читать код с 10 лет назад? Как В результате я стараюсь использовать простейший подход, когда делаю такие вещи, как объектов в C.

Задайте следующее:

  • Это для клиента с крайним сроком (если да, рассмотрите ООП)?
  • Могу ли я использовать ООП (часто меньше кода, быстрее разрабатывать, читать более)?
  • Можно ли использовать библиотеку (существующий код, существующие шаблоны)?
  • Я ограничена памятью или процессором (например, Arduino)?
  • Есть ли еще одна техническая причина использовать C?
  • Могу ли я сохранить C очень простым и удобочитаемым?
  • Какие функции OOP мне действительно нужны для моего проекта?

Я обычно возвращаюсь к чему-то вроде API GLIB, который позволяет мне инкапсулировать мой код и обеспечивает очень читаемый интерфейс. Если больше , я добавляю указатели функций для полиморфизма.

class_A.h:
  typedef struct _class_A {...} Class_A;
  Class_A* Class_A_new();
  void Class_A_empty();
  ...

#include "class_A.h"
Class_A* my_instance;
my_instance = Class_A_new();
my_instance->Class_A_empty();  // can override using function pointers

Ответ 5

Посмотрите реализация IJG. Они не только используют setjmp/longjmp для обработки исключений, они имеют vtables и все такое. Это хорошо написанная и достаточно маленькая библиотека, чтобы вы могли получить очень хороший пример.

Ответ 6

Подобно подходу Dale, но немного больше, чем Footgun, так это то, как PostgreSQL представляет узлы дерева разбора, типы выражений и т.д. внутренне. По умолчанию Node и Expr structs по строкам

typedef struct {
    NodeTag n;
} Node;

где NodeTag - это typedef для unsigned int, и есть заголовочный файл с набором констант, описывающих все возможные типы node. Сами узлы выглядят так:

typedef struct {
    NodeTag n = FOO_NODE;
    /* other members go here */
} FooNode;

и a FooNode могут быть отброшены на Node с безнаказанностью из-за причуды C-структур: если две структуры имеют одинаковые первые члены, они могут быть переданы друг другу.

Да, это означает, что a FooNode можно отнести к BarNode, который вы, вероятно, не хотите делать. Если вам нужна правильная проверка времени выполнения, GObject - это путь, хотя будьте готовы ненавидеть жизнь, пока вы ее повесили.

(примечание: примеры из памяти, я не взломал внутренности Postgres через некоторое время. FAQ для разработчиков содержит больше информации. )