Каким будет набор изящных препроцессорных хаков (совместимых с ANSI C89 / ISO C90), которые обеспечивают некую некрасивую (но пригодную для использования) объектную ориентацию в C?
Я знаком с несколькими различными объектно-ориентированными языками, поэтому, пожалуйста, не отвечайте с ответами типа «Изучите C ++!». Я прочитал « Объектно-ориентированное программирование с ANSI C » (будьте осторожны: формат PDF ) и несколько других интересных решений, но в основном меня интересует ваше :-)!
Смотрите также Можете ли вы написать объектно-ориентированный код на C?
Ответы:
C Object System (COS) звучит многообещающе (все еще в альфа-версии). Он пытается свести к минимуму имеющиеся концепции ради простоты и гибкости: единообразное объектно-ориентированное программирование, включающее открытые классы, метаклассы, метаклассы свойств, универсальные шаблоны, мультиметоды, делегирование, владение, исключения, контракты и замыкания. Есть черновик (PDF), который описывает это.
Исключение в С является C89-реализация TRY-CATCH-FINALLY, найденная в других языках OO. Он поставляется с комплектом тестов и некоторыми примерами.
Оба от Лорана Денио, который много работает над ООП в Си .
источник
Я бы посоветовал не использовать препроцессор (ab), чтобы попытаться сделать синтаксис C более похожим на синтаксис другого более объектно-ориентированного языка. На самом базовом уровне вы просто используете простые структуры в качестве объектов и передаете их указателями:
Чтобы получить такие вещи, как наследование и полиморфизм, вы должны работать немного усерднее. Вы можете сделать наследование вручную, если первый член структуры будет экземпляром суперкласса, а затем вы можете свободно использовать указатели на базовые и производные классы:
Чтобы получить полиморфизм (то есть виртуальные функции), вы используете указатели на функции и, необязательно, таблицы указателей на функции, также известные как виртуальные таблицы или vtables:
И вот как вы делаете полиморфизм в C. Это не красиво, но это делает работу. Существуют некоторые проблемы, связанные с приведением указателей между базовым и производным классами, которые безопасны, если базовый класс является первым членом производного класса. Множественное наследование намного сложнее - в этом случае, для того, чтобы найти между базовыми классами, отличными от первого, вам нужно вручную настроить указатели на основе правильных смещений, что действительно сложно и подвержено ошибкам.
Еще одна (хитрая) вещь, которую вы можете сделать, это изменить динамический тип объекта во время выполнения! Вы просто переназначаете новый указатель vtable. Вы можете даже выборочно изменять некоторые виртуальные функции, сохраняя другие, создавая новые гибридные типы. Просто будьте осторожны, создавая новый vtable вместо того, чтобы модифицировать глобальный vtable, иначе вы случайно затронете все объекты данного типа.
источник
struct derived {struct base super;};
, очевидно, угадывает, как это работает, так как по порядку байтов это правильно.Однажды я работал с библиотекой C, которая была реализована таким образом, что мне показалось довольно элегантным. Они написали в C способ определения объектов, а затем наследуют их, чтобы они были такими же расширяемыми, как объект C ++. Основная идея заключалась в следующем:
Наследовать сложно описать, но в основном это было так:
Тогда в другом файле:
Тогда вы можете создать фургон в памяти и использовать код, который знает только о транспортных средствах:
Он работал прекрасно, и файлы .h точно определяли, что вы должны делать с каждым объектом.
источник
Рабочий стол GNOME для Linux написан на объектно-ориентированном C и имеет объектную модель под названием « GObject », которая поддерживает свойства, наследование, полиморфизм, а также некоторые другие полезности, такие как ссылки, обработка событий (называемые «сигналы»), время выполнения. набор текста, личные данные и т. д.
Он включает в себя хаки препроцессора для выполнения таких вещей, как приведение типов в иерархии классов и т. Д. Вот пример класса, который я написал для GNOME (такие вещи, как gchar - это typedefs):
Класс Источник
Заголовок класса
Внутри структуры GObject есть целое число GType, которое используется в качестве магического числа для системы динамической типизации GLib (вы можете привести всю структуру к «GType», чтобы найти ее тип).
источник
Я делал подобные вещи в C, прежде чем я знал, что такое ООП.
Ниже приведен пример, который реализует буфер данных, который увеличивается по требованию, учитывая минимальный размер, приращение и максимальный размер. Эта конкретная реализация была основана на «элементах», то есть она была разработана для того, чтобы позволить подобную списку коллекцию любого типа C, а не только байтовый буфер переменной длины.
Идея состоит в том, что объект создается с помощью xxx_crt () и удаляется с помощью xxx_dlt (). Каждый из методов "member" принимает специально типизированный указатель для работы.
Таким образом я реализовал связанный список, циклический буфер и ряд других вещей.
Должен признаться, я никогда не задумывался о том, как реализовать наследование с помощью этого подхода. Я полагаю, что какая-то смесь того, что предлагал Кивели, могла бы стать хорошим путем.
dtb.c:
dtb.h
PS: vint был просто typedef от int - я использовал его, чтобы напомнить мне, что его длина варьируется от платформы к платформе (для портирования).
источник
Немного не по теме, но оригинальный компилятор C ++, Cfront , скомпилировал C ++ в C, а затем в ассемблер.
Здесь сохранились .
источник
Если вы думаете о методах, вызываемых для объектов, как о статических методах, которые передают неявный '
this
' в функцию, это может упростить процесс мышления в O.Например:
будет выглядеть так:
Или что-то вроде того.
источник
string->length(s);
ffmpeg (инструментарий для обработки видео) написан на простом C (и ассемблере), но с использованием объектно-ориентированного стиля. Он полон структур с указателями на функции. Существует набор фабричных функций, которые инициализируют структуры с помощью соответствующих указателей «метода».
источник
Если вы действительно думаете с любопытством, даже стандартная библиотека C использует ООП - подумайте
FILE *
в качестве примера:fopen()
инициализируетFILE *
объект, и вы используете его использовать методы - членовfscanf()
,fprintf()
,fread()
,fwrite()
и другие, и в конечном итоге завершить егоfclose()
.Вы также можете пойти по пути псевдо-Objective-C, который также не сложен:
Использовать:
Вот что может быть получено из некоторого кода Objective-C, подобного этому, если используется довольно старый переводчик Objective-C-C-C:
источник
__attribute__((constructor))
вvoid __meta_Foo_init(void) __attribute__((constructor))
?popen(3)
также возвращает aFILE *
для другого примера.Я думаю, что то, что написал Адам Розенфилд, является правильным способом выполнения ООП в C. Я хотел бы добавить, что он показывает реализацию объекта. Другими словами, фактическая реализация будет помещена в
.c
файл, а интерфейс - в заголовок..h
файл. Например, используя пример обезьяны выше:Интерфейс будет выглядеть так:
Вы можете увидеть в интерфейсе
.h
файле вы только определяете прототипы. Затем вы можете скомпилировать часть.c
файла "file" в статическую или динамическую библиотеку. Это создает инкапсуляцию, а также вы можете изменить реализацию по своему желанию. Пользователь вашего объекта должен почти ничего не знать о его реализации. Это также делает акцент на общий дизайн объекта.Я лично убежден, что oop - это способ концептуализации структуры вашего кода и возможности его повторного использования, который на самом деле не имеет ничего общего с теми другими вещами, которые добавляются в c ++, такими как перегрузка или шаблоны. Да, это очень хорошие полезные функции, но они не представляют, что такое объектно-ориентированное программирование.
источник
typedef struct Monkey {} Monkey;
Что это за определение типа после того, как оно было создано?struct _monkey
просто прототип. Фактическое определение типа определено в файле реализации (файл .c). Это создает эффект инкапсуляции и позволяет разработчику API переопределять структуру обезьян в будущем без изменения API. Пользователи API должны интересоваться только фактическими методами. Разработчик API заботится о реализации, в том числе о том, как устроен объект / структура. Таким образом, детали объекта / структуры скрыты от пользователя (непрозрачный тип).int getCount(ObjectType obj)
etc, если вы решите определить структуру в файле реализации.Моя рекомендация: будь проще. Одна из самых больших проблем, с которыми я сталкиваюсь, - это поддержка старого программного обеспечения (иногда старше 10 лет). Если код не простой, это может быть сложно. Да, можно написать очень полезный ООП с полиморфизмом в C, но это может быть трудно читать.
Я предпочитаю простые объекты, которые содержат некоторые четко определенные функциональные возможности. Отличным примером этого является GLIB2 , например, хеш-таблица:
Ключи:
источник
Если бы я собирался написать ООП в CI, вероятно, пошел бы с псевдо- Pimpl дизайн. Вместо того, чтобы передавать указатели на структуры, вы в конечном итоге передаете указатели на указатели на структуры. Это делает содержимое непрозрачным и облегчает полиморфизм и наследование.
Настоящая проблема с ООП в C состоит в том, что происходит, когда переменные выходят из области видимости. Деструкторов, генерируемых компилятором, не существует, и это может вызвать проблемы. Макросы, возможно, могут помочь, но смотреть на них всегда будет некрасиво.
источник
if
операторы и выпуская их в конце. Напримерif ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }
Другой способ программирования в объектно-ориентированном стиле на C - это использовать генератор кода, который преобразует язык, специфичный для предметной области, в C. Как это делается с помощью TypeScript и JavaScript, чтобы перенести ООП в js.
источник
Вывод:
Вот пример того, что такое ОО-программирование на C.
Это настоящий, чистый C, без макросов препроцессора. У нас есть наследование, полиморфизм и инкапсуляция данных (включая данные, частные для классов или объектов). У защищенного эквивалента квалификатора нет никаких шансов, то есть частные данные также являются частными в цепочке наследования. Но это не неудобство, потому что я не думаю, что это необходимо.
CPolygon
не создается, потому что мы используем его только для манипулирования объектами цепочки наследования, которые имеют общие аспекты, но имеют различную реализацию их (полиморфизм).источник
У @Adam Rosenfield есть очень хорошее объяснение того, как достичь ООП с C
Кроме того, я бы порекомендовал вам прочитать
1) pjsip
Очень хорошая библиотека C для VoIP. Вы можете узнать, как он достигает ООП, хотя структуры и таблицы указателей на функции
2) iOS Runtime
Узнайте, как среда выполнения iOS поддерживает Objective C. Он достигает ООП с помощью указателя isa, мета-класса.
источник
Для меня объектная ориентация в C должна иметь следующие особенности:
Инкапсуляция и сокрытие данных (может быть достигнуто с помощью структур / непрозрачных указателей)
Наследование и поддержка полиморфизма (одиночное наследование может быть достигнуто с помощью структур - убедитесь, что абстрактная база не является экземпляром)
Функциональность конструктора и деструктора (нелегко достичь)
Проверка типов (по крайней мере, для пользовательских типов, так как C не применяет их)
Подсчет ссылок (или что-то для реализации RAII )
Ограниченная поддержка обработки исключений (setjmp и longjmp)
Помимо вышесказанного, он должен опираться на спецификации ANSI / ISO и не должен полагаться на специфичные для компилятора функциональные возможности.
источник
Посмотрите на http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html . Если ничто иное, как чтение документации, не является полезным опытом.
источник
Я немного опоздал на вечеринку здесь, но мне нравится избегать обеих крайностей макросов - слишком много или слишком много запутывает код, но пара очевидных макросов может облегчить разработку и чтение кода ООП:
Я думаю, что у этого есть хороший баланс, и ошибки, которые это генерирует (по крайней мере с опциями gcc 6.3 по умолчанию) для некоторых из более вероятных ошибок, полезны вместо того, чтобы путать. Весь смысл в повышении производительности труда программиста нет?
источник
Если вам нужно написать немного кода, попробуйте это: https://github.com/fulminati/class-framework
источник
Я также работаю над этим на основе макро решения. Так что это только для самых смелых, я думаю ;-) Но это уже довольно приятно, и я уже работаю над несколькими проектами в дополнение к этому. Это работает так, что вы сначала определяете отдельный заголовочный файл для каждого класса. Как это:
Чтобы реализовать класс, вы создаете для него заголовочный файл и файл C, в котором вы реализуете методы:
В заголовок, который вы создали для класса, вы включаете другие нужные вам заголовки и определяете типы и т. Д., Относящиеся к классу. И в заголовке класса, и в файле 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), который выполняет эту работу. Если вы используете это, вы можете просто написать
и он создаст файл спецификации класса, заголовок класса и файл C, в котором
Point_impl.c
указано, где вы реализуете класс. Это экономит много работы, если у вас много простых классов, но все еще в C. wobject - очень простой сканер на основе регулярных выражений, который легко адаптировать к конкретным потребностям или переписать с нуля.источник
Проект с открытым исходным кодом Dynace делает именно это. Это на https://github.com/blakemcbride/Dynace
источник
Вы можете попробовать COOP , дружественную к программисту среду для ООП в C, которая включает классы, исключения, полиморфизм и управление памятью (важно для встроенного кода). Это относительно легкий синтаксис, см. Учебник в Wiki там.
источник