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

Объектно-ориентированное программирование на C

Возможные дубликаты:
Можете ли вы написать объектно-ориентированный код в C?
Объектно-ориентированный шаблон в C?

Я помню, как некоторое время назад читал о ком-то (я думаю, это был Линус Торвальдс) о том, как С++ - это ужасный язык и как вы можете писать объектно-ориентированные программы с C. На то, что успел задуматься, я не действительно увидеть, как все объектно-ориентированные понятия переносятся на C. Некоторые вещи довольно очевидны. Например:

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

Как бы вы эмулировали инкапсуляцию и наследование?

Я предполагаю, что инкапсуляция может быть эмулирована путем размещения вложенной структуры, в которой хранятся частные члены. Было бы довольно легко обойти, но его можно было бы назвать PRIVATE или что-то столь же очевидное, чтобы сигнализировать, что он не предназначен для использования извне структуры. А как насчет наследования?

4b9b3361

Ответ 1

Вы можете реализовать полиморфизм с регулярными функциями и виртуальными таблицами (vtables). Здесь довольно аккуратная система, которую я придумал (на основе С++) для упражнений по программированию: alt text

Конструкторы выделяют память, а затем вызывают функцию init класса, в которой инициализируется память. Каждая функция init также должна содержать статическую структуру vtable, содержащую указатели виртуальных функций (NULL для чистого виртуального). Производные функции init класса вызывают функцию суперкласса init, прежде чем делать что-либо еще.

Очень хороший API может быть создан путем реализации оболочек виртуальных функций (не путать с функциями, на которые указывает vtables) следующим образом (добавьте static inline перед ним, если вы сделаете это в заголовке)

int playerGuess(Player* this) { return this->vtable->guess(this); }

Одиночное наследование может быть выполнено путем злоупотребления двоичной компоновкой структуры: alt text

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

Другие данные, относящиеся к конкретному типу, также могут быть добавлены к виртуальным таблицам. Примеры включают информацию типа времени выполнения (например, имя типа в виде строки), связь с суперклассом vtable и цепочкой деструкторов. Вероятно, вам нужны виртуальные деструкторы, где деструктор производного класса понижает объект до его суперкласса, а затем рекурсивно вызывает деструктор этого и т.д., Пока не будет достигнут деструктор базового класса и который, наконец, освободит структуру.

Инкапсуляция была выполнена путем определения структур в player_protected.h и реализации функций (обозначенных vtable) в player_protected.c и аналогичным образом для производных классов, но это довольно неуклюжие, и это ухудшает производительность (поскольку виртуальные обертки не могут быть помещены в заголовки), поэтому я рекомендовал бы против этого.

Ответ 3

Как бы вы эмулировали инкапсуляцию и наследование?

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

Например, Windows FILE api полностью инкапсулирован. Когда вы открываете файл, вы возвращаете непрозрачный объект, который содержит всю информацию о состоянии для файла "объект". Вы передаете этот дескриптор каждому файлу io apis. Инкапсуляция на самом деле намного лучше, чем С++, потому что нет общедоступного заголовочного файла, на который люди могут смотреть и видеть имена ваших частных переменных.

Наследование сложнее, но это совсем не обязательно для того, чтобы ваш код был объектно ориентированным. В некотором смысле агрегирование в любом случае лучше наследования, а агрегация так же легко в C, как и на С++. см. этот, например.

В ответ на Нейл см. Wikipedia для объяснения того, почему наследование не требуется для полиморфизма.

Нас старожилы написали объектно-ориентированный код за годы до того, как были доступны компиляторы С++, это набор не набор инструментов.

Ответ 4

Основа CoreFoundation на основе Apple C была написана так, что ее "объекты" могли бы удвоиться как объекты в Objective-C, реальном языке OO. Довольно большое подмножество структуры является открытым исходным кодом на сайте Apple как CF-Lite. Возможно, это будет полезное тематическое исследование в рамках основной OS-структуры, выполненной таким образом.

Ответ 5

От немного более высокой высоты и рассматривая проблему скорее более непредвзятой, чем, как может предложить основной принцип ООП, объектно-ориентированное программирование означает мышление об объектах как с данными со связанными функциями. Это не обязательно означает, что функция должна быть физически привязана к объекту, поскольку она находится в популярных языках, которые поддерживают парадигму ООП, например, в С++:

struct T
{
   int data;
   int get_data() const { return data; }
};

Я бы предложил более подробно рассмотреть GTK + Object and Type System. Это блестящий пример ООП, реализованный на языке программирования C:

GTK + реализует свой собственный пользовательский объект система, которая предлагает стандартные объектно-ориентированные функции, такие как наследование и виртуальная функция

Ассоциация также может быть контрактной и условной.

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

Другой, похожий, но отличный Shadow Data type - проверьте эту ссылку, где Джон Джаггер дает отличное объяснение этой не очень известной методике.

Ответ 6

библиотеки gtk и glib используют макросы для создания объектов для разных типов.
add_widget (GTK_WIDGET (MyButton));
Я не могу сказать, как это было сделано, но вы можете прочитать их источник, чтобы узнать, как это делается.

Ответ 7

Посмотрите, как работает слой VFS в ядре Linux для примера шаблона наследования. Файловые операции для различных файловых систем "наследуют" набор общих функций операций с файлами (например, generic_file_aio_read(), generic_file_llseek()...), но могут переопределять их с их собственными реализациями (например, ntfs_file_aio_write()).

Ответ 8

Определенно посмотрите на Objective-C.

typedef struct objc_object {
    Class isa;
} *id;

typedef struct objc_class {
    struct objc_class *isa;
    struct objc_class *super_class
    const char *name;
    long version;
    long info
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
   struct objc_cache *cache;
   struct objc_protocol_list *protocols;
} *Class;

Как вы можете видеть, информация о наследовании вместе с другими деталями хранится в структуре класса (удобно класс также можно рассматривать как объект).

Objective-C страдает так же, как С++ с инкапсуляцией, поскольку вам нужно публично объявлять свои переменные. Straight C намного более гибкий, поскольку вы можете просто вернуть указатели void, к которым только ваш модуль имеет внутренний доступ, поэтому в этом отношении инкапсуляция намного лучше.

Я как-то написал базовую программу рисования C-стиля OO как часть графического курса - я не дошел до объявления класса, я просто использовал указатель vtable в качестве первого элемента структуры и реализовал ручной код наследование. Оптимальная игра с vtables на таком низком уровне заключается в том, что вы можете изменять поведение класса во время выполнения, изменяя несколько указателей или динамически изменяя класс объектов. Было довольно легко создавать всевозможные гибридные объекты, поддельные множественные наследования и т.д.

Ответ 10

Для отличного примера объектно-ориентированного программирования на C посмотрите на источник POV-Ray несколько лет назад - версия 3.1g особенно хороша. Конечно, "объекты" были структурой с указателями функций. Макросы были использованы для предоставления основных методов и данных для абстрактного объекта, а производными классами были структуры, которые начинались с этого макроса. Однако не было попытки разобраться с частным/общественным. Все, что нужно было видеть, было в файлах .h, а детали реализации были в .c файлах, в основном, за исключением множества исключений.

Были некоторые опрятные трюки, которые я не вижу, как можно переносить на С++ - например, преобразование одного класса в другое, но похожего "на лету" путем переназначения указателей на функции. Простые на сегодняшний день динамические языки. Я забыл детали; Я думаю, что это могли быть пересечения CSG и объекты объединения.

http://www.povray.org/

Ответ 11

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

Ответ 12

Один способ обработки наследования - наличие вложенных структур:

struct base
{
    ...
};

void method_of_base(base *b, ...);

struct child
{
    struct base base_elements;
    ...
};

Затем вы можете делать такие вызовы:

struct child c;
method_of_base(&c.b, ...);

Ответ 13

Возможно, вы захотите посмотреть Objective-C, что в значительной степени то, что он делает. Это просто интерфейс, который компилирует Objective-C код OO на C.