Как стать автором
Обновить

Объектно ориентированное программирование на Си без плюсов. Часть 1. Введение

Время на прочтение5 мин
Количество просмотров24K
Всего голосов 16: ↑15 и ↓1+14
Комментарии41

Комментарии 41

И даже без макросов. Но ведь так не интересно

Да, без макросов не интересно, потому что макросы очень мощная штука, фактически можно синтаксис написания ПО перестроить под себя. Я, например не знаю красивого решения для передачи типа переменной в функцию, а в макросе можно явно, без кавычек и т.д., написать MYMAKROS(..., int, float);

#define true false - вот она силища!

Уже 10 лет (с выхода C11) так никто не делает. Для этого есть _Generic

Слишком много дополнительного кода, за которым надо следить.

Цель не ясна.

Можно писать на ванильном С++ без STL, если так хочется.

На Java тоже много кода получается. Те же конструкторы ручками прописывать, "сеттеры", "геттеры", интерфейсы ...

То, что за кодом надо следить конечно соглашусь, но это же Си.

Конечно можно писать на Си++, я же не спорю. И оказывается без плюсов тоже.

Знаю, согласен.

Складывается впечатление что автор пытается изобрести Golang.

Нет. Изобретать ничего не собираюсь. Просто для себя решил ответить на вопрос нужны ли мне ++ к Си. Если смотреть то, как решаются многие задачи, особенно для микроконтроллеров, то от Си++ фактически используют только компилятор, а если использовать не ООП, а автоматный стиль программирования, то зачем тогда вообще плюсы?

Но по сути получается очень близко к тому, что делается в Golang. Только в нем синтаксически это более красиво выглядит.

НЛО прилетело и опубликовало эту надпись здесь

присоединяюсь к комментариям выше - ради повышенной типобезопасности

На микроконтроллерах сейчас можно запустить раст и забыть об ужасах С++ (да и Си).

Но, понимаю, это слишком просто, да еще и учить что-то новое надо.

Посмотрите на Vala. Там прямой транслятор в С на основе GLib. Все плюшки и синтаксический сахар работают из коробки. Код на выходе обмазан всевозможными проверками, поэтому в случае падения программы вместо просто коредамп дает вполне вразумительную информацию о причине вылета.

Инкапсуляция. Смысл её в том, что бы разделить частное (protected, private … ) и общедоступное ( public, published … ). Частное это внутренняя «кухня» определённого класса доступ до которой ограничен.

Позвольте не согласиться с вашим определением. То, что вы описали — это сокрытие, которое может быть деталями конкретной реализации принципа инкапсуляции. Но инкапсуляция — это не сокрытие. Это размещение в одном компоненте данных и методов, которые с ними работают.


Например, в Python есть ООП и есть инкапсуляция, но сокрытия как такового нет или оно декларируется исключительно на уровне конвенции именования.

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

Для себя я определяю инкапсуляцию просто как хороший дизайн API, когда "кишки", состояние и т. п. не торчат наружу из объекта. Объект не предоставляет к ним доступ посредством публичного API, это его внутренняя кухня, она может быть в каком угодно состоянии, и пользователи/клиенты, использующие API не должны о ней знать, не должны от неё зависеть или завязывыаться на неё своим поведением.


Механизм сокрытия и невозможности доступа обеспечивает дизайн конкретного языка программирования. В Java, C++ есть разделение на public, private, protected, что действительно жёстко ограничивает доступ к внутренностям объекта. Там же считается, что инкапсуляция без сокрытия неполноценна. Но это не везде так. Лично на мой взгляд, сокрытие нельзя считать инкапсуляцией как таковой в полной мере, потому что имея механизм сокрытия всё равно можно спроектировать такой публичный API, который будет нарушать инкапсуляцию как принцип проектирования.

Почему не GObject?

У Нила Брауна есть интересный цикл статей про ООП-паттерны в ядре Linux. Там рассмотрены несколько реализаций, с их плюсами и минусами. Многие из них лишены недостатков, которые есть в вашей.

Да, конечно, спасибо, про потерны знаю, но сначала хочу по "граблям" сам пройти.

Я знал людей, которые "Объектно ориентированно программировали" на FORTRAN-4

Перед новым определяемым типом пишется « t_ », например: t_mynewtype;

Почему именно «перед» — `t' всегда писалось после, например, так:
mynewtype_t
В Си нет понятия классов

Не переживайте, в стандарте C++ в разделе «Classes» написано дословно, что «A class is a type». Так что слово «класс» следует понимать, как синоним слова «тип».

Не совсем так, любой класс - это тип, но не любой тип - это класс, так ведь?

Я согласен с тем, что ООП вообще скорее это абстрактный стиль написания программы и что его можно реализовывать и на языках не имеющих встроенной его поддержки. Описанную автором методику я применял при разработке некоторых модулей к своей системе, например ядра расчета электрических цепей, которое делалось на годом Си. Зачем так было сделано: чтобы обеспечить кросс языковую структурную совместимость, то есть чтобы без проблем вызывать сишный код из Delphi/fpc и из c/c++, но при этом чтобы код был объектно ориентированный по сути. В принципе это работает и можно делать код несколькими способами, подход даёт повышенную гибкость. Из минусов хочу отметить, что оптимизатор компилятора Си может сделать код при таком подходе менее оптимальным чем если например делать его именно как объекты на плюсах или. Delphi. Проблема только потом что объектная модель компиляторов различается и с совместимостью придется извращаться если в проекте их несколько разных. Если же везде только одни плюсы и один компилятор, всё решается наследованием от абстрактного класса, который описывается в общем интерфейсном заголовочном файле.

Для саморазвития, это нормально. А с практической точки зрения, кому это нужно? Кто при здравом уме, этим будет пользоватся? Это что то вроде, как написание своей собственной операционной системы. Есть такие чудики, пишут. Но вот беда, их ОС, ни кто не использует, кроме как запустить ради любобытства.

Да приходится вообще иногда и такое делать. Могу пример скинуть реализации этого подхода на Delphi и C для конкретной расчетной библиотеки.

В ООП стиле есть разные библиотеки на С. Например, libiec61850, вот пример кода из ее header файла оттуда:
/**
* \brief Create a new configuration object
*
* \return a new configuration object with default configuration values
*/
LIB61850_API IedServerConfig
IedServerConfig_create(void);

/**
* \brief Destroy the configuration object
*/
LIB61850_API void
IedServerConfig_destroy(IedServerConfig self);

/**
* \brief Set the IEC 61850 standard edition to use (default is edition 2)
*
* \param edition IEC_61850_EDITION_1, IEC_61850_EDITION_2, or IEC_61850_EDITION_2_1
*/
LIB61850_API void
IedServerConfig_setEdition(IedServerConfig self, uint8_t edition);

/**
* \brief Get the configued IEC 61850 standard edition
*
* \returns IEC_61850_EDITION_1, IEC_61850_EDITION_2, or IEC_61850_EDITION_2_1
*/
LIB61850_API uint8_t
IedServerConfig_getEdition(IedServerConfig self);

/**
* \brief Set the report buffer size for buffered reporting
*
* \param reportBufferSize the buffer size for each buffered report control block
*/
LIB61850_API void
IedServerConfig_setReportBufferSize(IedServerConfig self, int reportBufferSize);

Сам пользовался ей в одном проекте, очень хорошая качественная либа, с чистыми исходниками. Не вижу проблем в ООП походе.

Портируемость максимальная, писать C++ обертки над такими либами — одно удовольствие.
В деструкторе присваивать NULL локальному указателю бессмысленно.
Есть книга, Axel-Tobias Schreiner, «Object-Oriented Programming with ANSI-C».
И перевод её на русский.

События к ООП как относятся? Реализовать их можно и на ООП языках тоже, не спорю. Но связи не вижу.

А чем вам GObject не угодил?

Проблема C не столько в отсутствии ООП, сколько в том, что слишком много вещей приходится делать вручную (и соответственно легко забыть их сделать).
Так, если в C++ при выходе стековой переменной за пределы видимости деструктор вызовется автоматически, то в C вам придётся делать это вручную, и в мало-мальски крупном проекте вы хоть где-нибудь это сделать забудете с вероятностью, равной 100%.

А потом даже можно написать:

...

Компилятор такую запись должен понять

Интересно, какой это компилятор такое может понять? Это противоречит стандарту языка.

И, кстати, на будущее - никогда не надо исходники лепить как картинки, это сильно усложняет возможность копировать текст, не говоря уж о слабовидящих людях, которые используют screenreader'ы. На SO, например, за такое минусуют нещадно.

никогда не надо исходники лепить как картинки,


Скажите это редакторам Хабра и песочнице, не знаю как сегодня но пару лет назад было иначе никак) никакие сторонние подсветки кода в песочнице не вставить было, там вообще возможностей меньше чем в обычных статьях. Так что думаю конкретно тут не вина автора.

В соответствии с принятой нотацией типовой конструктор это функция, которая может выглядеть, как-то так:

не "конструктор", а "фабричная функция".

Буквально в первой же КДПВ вижу 3 ошибки: 1. функция возаращает t_mynewclass, ниже return new_object, где new_object - t_mynewclass*. 2. exception не проверяется против NULL. Если он никогда не NULL, то лучше возвращать структуру вида struct S { t_mynewclass* obj; int error; }; 3. free(object) вместо free(new_object). Объекты кстати не всегда рационально выделять в куче. Ну и еще по мелочи - при такой организации кода фабричной функции будет жуткий бойлерплейт обработки ошибок если надо заполнить сразу много динамически аллоцированных полей . Имо это лучше делается через goto, как-то так, но это уже вкусовщина.

Хорошим стилем считается вместе с callback'ами передавать void* context, инода даже deleter для этого контекста. Иначе получится весьма сомнительный ООП.

Не дает покоя вопрос "зачем". Учитывая что существует с++, вы переизобретаете велосипед с кучей бойлерплейта и простором для глупых ошибок.

Мои познания Windows закончились на «WinXP», после которой в Windows стало уже очень много политики («безопасности») и коммерческой составляющей

В этом смысле удобнее работать с серверными версиями Windows, настроенных, как десктопные.

Статья рассчитана на тех кто уже знаком с Си, а все примеры ориентированы на ОС Linux.

Сейчас много опенсорса разработанного на Линуксном Си. Но любителям «форточек» приходится конвертировать код под Виндоуз, для бесплатных компиляторов, типа MS Visual Studio C++ Community Edition (там требуется только регистрация). Можно не любить M$, но компиляторы VS C++ у него хорошие.

Что меня угнетает в примерах Си под Линукс, так это нелюбовь к оконному интерфейсу. Только консоль, только хардкор. Хотя опенсорный код там встречается замечательный. Например, FFplay.c. Это «консольный» видеопроигрыватель. Точнее видео проигрывается в окне, с поддержкой большинства кодеков, а управление им осуществляется в консоли. Когда понадобилось портировать код под C++ / WTL в Windows, то пришлось изрядно повозиться. Но получилось! Видео отображается в клиентской части главного окна (разделенного сплитером), в отдельном потоке. В левой части отображается текущий каталог с файлами мультимедиа. Программа делается как обучающая. Само видео работает великолепно!

Поэтому, лично я сторонник С++. Изумительный язык и превосходные компиляторы под него.
Что мне понравилось, это то, что автор сразу говорит о том, что ООП — это прежде всего абстрактная парадигма. Соответственно, можно организовать код на языке без ООП таким образом, что оно будет выглядет как работа с объектами, даже если их в принципе не может быть. Вот, например, на баше — stackoverflow.com/questions/36771080/creating-classes-and-objects-using-bash-scripting (особенно первый комментарий ко второму ответу)
Это не делает сам язык объектно-ориентированным, но, тем не менее, позволяет создать внешнее представление, отражающее саму концепцию. И это интересно, в первую очередь, с точки понимания самой концепции ООП, даже если не очень практично.

В ООП методы класса не видны снаружи класса. В вашем же случае хендлер (метод вашего класса, получается) в глобальной видимости. Смысл ? Кроме того, для нового класса придется придумывать новое имя хэндлера и он, опять же будет глобальный

>не увидел, чем не устраивает с++
>не увидел примера кода/репозитория с такими классами

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории