2009-08-04 1 views
7

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

+1

Хотя я всегда использовал то, что я думаю, вы могли бы назвать «объект на основе» стиль программирования C, это гораздо, гораздо проще в этом (и почти все остальное) в C++. Я должен спросить, почему вы, похоже, не хотите его использовать? – 2009-08-04 07:55:25

+1

@Neil: Если бы не было причин, не было бы никакого GObject. Существует много причин для C: устаревших платформ, устойчивости, двоичной совместимости ABI, вы называете это. – EFraim

+0

@EFraim Я хорошо осведомлен об этих причинах, но я спрашивал, каковы причины допроса. – 2009-08-04 08:57:31

ответ

4

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

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

Вы часто также ручной реализации виртуальных таблиц, как в "COM in plain C"

+0

EFraim - Не могли бы вы описать стратегию реализации GObject в своем ответе, чтобы мы могли сравнить ее с некоторыми другими решениями? – SetJmp

0

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

+0

Знаете ли вы, что они используют реализацию, такую ​​как предложенная Дейлом и Фалаиной? Или что-то более динамичное? – SetJmp

+0

Да, очень похоже. – cheez

9

Я стараюсь сделать что-то вроде этого:

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, ...); 

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

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

7

Ваше использование термина «объекты» немного расплывчато, поэтому я собираюсь предположить, что вы спрашиваете, как использовать 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; 
} 

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

+0

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

0

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

typedef struct { 
    NodeTag n; 
} Node; 

где NodeTag представляет собой ЬурейеЕ для неподписанных Int, и есть заголовочный файл с кучей констант, описывающих все возможные типы узлов. Узлы сами выглядеть следующим образом:

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

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

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

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

+0

Долгое время назад (около 1988 года) я работал над компилятором C, где узлы синтаксического анализа были небольшим объединением отдельных типов узлов, с тегом типа вне союза, чтобы решить, какая ветвь объединения действительна: это морально эквивалентно тому, что вы описываете. Сегодня я почти наверняка сделал это в соответствии с моим предложением выше. Я считаю, что использование указателей функций действительно заставляет меня определять желаемый общедоступный api - поскольку вызывающий абонент не знает имя вызываемой функции, ему очень трудно достичь этой функции, чтобы использовать внутренние данные об этой реализации. –

+0

Sigh ... s/bit union/большой союз/выше. Извините за опечатку. –

+0

Метод указателя функции определенно превосходен - с одной стороны, он позволяет вам аппроксимировать методы привязки к объекту. Мне нравятся ваши примеры и проголосовали за это. –

2

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

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

Спросите следующее:

  1. Является ли это для клиента с крайним (если это так, рассмотрим ООП) ?
  2. Могу ли я использовать ООП (часто меньше кода, быстрее разрабатывать, более читаемо)?
  3. Могу ли я использовать библиотеку (существующий код, существующие шаблоны)?
  4. Я ограничена памятью или процессором (например, Arduino)?
  5. Есть ли еще одна техническая причина для использования C?
  6. Могу ли я сохранить свой C очень простым и удобочитаемым?
  7. Какие функции 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