Как вам такая демонстрация идеи программиования объектов на Си в стиле Go?Тут поддерживается скрытие приватных полей и композиция методов.
goc.h
#ifndef GOC_H
#define GOC_H#define fn(obj, fn, ...) (obj).fn(&(obj), ##__VA_ARGS__)
#endif
woman.h
#ifndef WOMAN_H
#define WOMAN_H#include "goc.h"
struct woman;
typedef struct w {
struct woman *w;
void (* hug)(struct w *self);
void (* kiss)(struct w *self);
} woman;woman new_woman(char *);
void free_woman(woman *);#endif
woman.c
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>#include "woman.h"
struct woman {
char *name;
bool hugged;
bool kissed;
};void hug(woman *self) {
if (self->w == NULL) {
printf("No woman allocated!\n");
return;
}printf("You're hugging %s\n", self->w->name);
if (self->w->hugged) {
printf("%s say: It hurts, get off me!\n", self->w->name);
return;
}
if (self->w->kissed) {
printf("%s say: Get off me, butch!\n", self->w->name);
return;
}self->w->hugged = true;
printf("%s say: you're sweet!\n", self->w->name);
}void kiss(woman *self) {
if (self->w == NULL) {
printf("No woman allocated!\n");
return;
}printf("You're kissing %s\n", self->w->name);
if (!self->w->hugged) {
if (self->w->kissed) {
printf("%s is calling the police!\n", self->w->name);
return;
}
self->w->kissed = true;
printf("%s say: How dare you?\n", self->w->name);
return;
}
if (self->w->kissed) {
printf("%s say: Level up, boy!\n", self->w->name);
return;
}self->w->kissed = true;
printf("%s say: Mwah!\n", self->w->name);
}woman new_woman(char *name) {
struct woman *w = calloc(1, sizeof(struct woman));
w->name = name;
woman women = {
.w = w,
.hug = hug,
.kiss = kiss
};
return women;
}void free_woman(woman *w) {
free(w->w);
w->w = NULL;
}main.c
#include <stdio.h>#include "woman.h"
int main(void)
{
woman woman;printf("\033[1mThe right workflow:\033[0m\n");
woman = new_woman("Alice");
fn(woman, hug);
fn(woman, kiss);
fn(woman, kiss);
free_woman(&woman);putchar('\n');
printf("\033[1mThe wrong workflow:\033[0m\n");
woman = new_woman("Miranda");
fn(woman, kiss);
fn(woman, hug);
fn(woman, kiss);
free_woman(&woman);putchar('\n');
printf("\033[1mThe dreaming workflow:\033[0m\n");
fn(woman, hug);
fn(woman, kiss);
fn(woman, kiss);return 0;
}Программа печатает:
The right workflow:
You're hugging Alice
Alice say: you're sweet!
You're kissing Alice
Alice say: Mwah!
You're kissing Alice
Alice say: Level up, boy!The wrong workflow:
You're kissing Miranda
Miranda say: How dare you?
You're hugging Miranda
Miranda say: Get off me, butch!
You're kissing Miranda
Miranda is calling the police!The dreaming workflow:
No woman allocated!
No woman allocated!
No woman allocated!
RAII лучше реализуй. Автоматическое или полуавтоматическое. Оно полезнее будет.
typedef woman и struct woman - лучше чтоб различались по имени.Композиции методов я не увидел, только инсталляцию методов.
Сокрытие закрытых полей ... Тут есть доступ к полю w, и через него можно повредить данные в w.
Think different - это не проблема, но лучше так не делать. Для программирования объектов на С лучше применяйте традиционный способ - модульное программирование, как в Modula-2.
> typedef woman и struct woman - лучше чтоб различались по имени.Они всё равно в разных пространствах имён и перепутать их трудно.
> Композиции методов я не увидел, только инсталляцию методов.
В чём разница?
> Сокрытие закрытых полей ... Тут есть доступ к полю w, и через
> него можно повредить данные в w.Только через грязные хаки с адресной арифметикой. Нормального доступа к его полям нет.
> Think different - это не проблема, но лучше так не делать. Для
> программирования объектов на С лучше применяйте традиционный способ - модульное программирование,
> как в Modula-2.Каким образом? Основная мотивация прикрепить функцию к объекту - перенести её из глобального пространства имён в пространство имён конкретной функции. При этом в Си, к сожалению, нет механизма автоматической передачи ссылки или копии объекта через первый аргумент функции (в Go это называется ресивер). Именно поэтому пришлось городить макрос fn.
Идея понятна и макрос fn выглядит аккуратно, но цена — лишняя сложность и риск поломать инварианты. Для C модульный подход всё же читается и сопровождается проще😊
> Основная мотивация прикрепить функцию к объекту - перенести её из глобального пространства имён в пространство имён конкретно[го инстанса]В глобальном пространстве имен ничего плохого нет. Ты столкнулся с синтаксическим неудобством. Синтаксические проблемы должны решаться на уровне синтаксиса, а не на уровне рантайма. "Достать указатель на функцию, а затем ее вызвать" -- это дороже, чем просто "вызвать уже известную функцию".
Подход с прикреплением функций к структу -- распространен. Есть сценарии, где такой подход разумен. Например, библиотека по парсингу XML может получать от тебя структ, который ты заполнишь функциями типа open_tag, data, close_tag и т. д., а библиотека далее будет твои функции вызывать в процессе парсинга XML. Но в твоем случае ничего такого нет. Единственное, что ты тут преследуешь -- это синтаксическое удобство. Не надо так. Применяй инструменты сообразно задачам.
> Ты столкнулся с синтаксическим неудобством. Синтаксические проблемы должны решаться на уровне синтаксиса, а не на уровне рантайма.Да, после таких языков как Java и теперь Go писать чисто процедурно как-то неудобно. Хотя когда-то я начинал вообще с Паскаля, где программа по Вирту - это всего лишь алгоритмы и структуры данных.
> "Достать указатель на функцию, а затем ее вызвать" -- это дороже, чем просто "вызвать уже известную функцию".
Практически все языки, оперирующие объектами так делают, например C++.
> Подход с прикреплением функций к структу -- распространен. Есть сценарии, где такой подход разумен. Например, библиотека по парсингу XML может получать от тебя структ, который ты заполнишь функциями типа open_tag, data, close_tag и т. д., а библиотека далее будет твои функции вызывать в процессе парсинга XML. Но в твоем случае ничего такого нет. Единственное, что ты тут преследуешь -- это синтаксическое удобство. Не надо так. Применяй инструменты сообразно задачам.
Ну да, мне выше уже посоветовали модули, то есть, если я правильно понял, единые блоки трансляции (SCU). Но API любого такого модуля всё равно попадёт в глобальное пространство имён и какая нибудь функция open() тут же начнёт конфликтовать с одноимёнными функциями из других модулей или даже из libc. Для того, чтобы это обойти можно использовать префикс, например abc_open() где abc - название модуля. Но это же неудобно и менее читаемо. В том же C++ помимо методов класса есть просто пространства имён, внутри которых можно определить функции с любыми именами, не опасаясь конфликта этих имён. К сожалению в Си этого нет. Возможно я просто пытаюсь перенести свои привычки из других языков в Си, но согласись, что префиксы - это действительно неудобно.
Всё верно. Просто язык С предназначен только лишь для написания эффективных процедур по обработке байтов, машинных слов и их последовательностей, а не для создания целых программ.
> Всё верно. Просто язык С предназначен только лишь для написания эффективных процедур
> по обработке байтов, машинных слов и их последовательностей, а не для
> создания целых программ.Как раз целые программы на Си раньше писали. Например первый веб браузер NCSA Mosaic или почтовый сервер Sendmail. Поколение программистов сменилось, а с ним и популярность парадигм программирования, но в последнее время наметился отход от ООП, что немного прибавляет популярности и чистому Си.
> все языки, оперирующие объектами так делают, например C++Именно в C++, дефолтным является вариант "вызвать уже известную функцию", без вычисления ее адреса в рантайме, даже если со стороны выглядит как вызов функции-в-структе (dog->bark()). Гуглить static dispatch vs. dynamic dispatch. В расте примерно то же самое. В таких языках компилятор решает проблему на уровне синтаксиса, не жертвуя скоростью в рантайме.
> это же неудобно и менее читаемо
Да. За удобством лучше сразу идти в другие языки, например в раст. Неудобный и нечитабельный вариант с префиксами -- это стандарт де-факто в примерно всех сишных библиотеках. Рантайм-скорость там просто ценят выше, чем удобство разработки.
>> все языки, оперирующие объектами так делают, например C++
> Именно в C++, дефолтным является вариант "вызвать уже известную функцию", без вычисления
> ее адреса в рантайме, даже если со стороны выглядит как вызов
> функции-в-структе (dog->bark()).Я имел в виду виртуальные функции.