LINUX.ORG.RU

Реализация методов в Си

 , ,


1

6

Всем привет!
Учу язык Си (не С++), дошёл до структур, и они мне так напомнили уже привычное ООП, с методами.
Решил, что можно ведь реализовать «Классы» и «Методы» и в обычном Си, объявляя указатели на функции в структурах.
Да, колхозно, да можно выбрать С++, а не Си. Но просто хотелось узнать мнение бывалых Си'шников, насколько это адекватный подход (1), и вообще делают ли так (2), и как можно улучшить написанный мной пример ниже (3)?:

#include <stdio.h>

int xsum(int x, int y);

int xsum(int x, int y)
{
        return x + y;
}

int main()
{

        struct point
        {
                int x;
                int y;
                int (*xysum)(int x, int y);
        };

        struct point pt;

        pt.x = 320;
        pt.y = 200;
        pt.xysum = &xsum;


        printf("X: %d\nY: %d\nSum: %d\n", pt.x, pt.y, pt.xysum(pt.x, pt.y));

        return 0;
}

Выводит:
X: 320
Y: 200
Sum: 520

Ответ на: комментарий от anonymous

в первом приближении, нечто вроде полиморфизма можно изобразить на структурах в стиле Win32 API. это когда сначала идёт поле tagSTRUCT с размером, а потом (через union или через char x[0] а по факту реальной длины tagSTRUCT), и структуры выровнены так, чтобы ABI не поломался (то есть, новые поля в потомках дописываются строго в конец).

но опять же, без таблицы диспетчеризации в каком-то виде (типа С++ -ной VTable, или деревьев диспетчеризации как в Nim или читай чего в COS) ты не сделаешь быстрый предикат is-a с учётом потомков, а значит, не сделаешь быструю динамическую типизацию.

поэтому это скорее объектная модель Oberon (без Oberon-2 с fixup-ами, просто методы как указатели) у тебя получается.

anonymous
()
Ответ на: комментарий от anonymous

поэтому так обычно и не делают.

ежели же тебе забить на полиморфизм с динамической типизацией, наследование (а зачем тогда тебе ООП?) — см. например в ядро, в расширения (модули ядра как ООП система, вместо указателей в рантайме (как в SOM при инициализации инстанса) получается указатели на static методы (и болт с наследованием модулей). ну или сразу в GObject смотри (напиши свой пример на Vala и в Си с ключиком valac --ccode helloworld.vala оттранслируй, и читай скогдогенерированный Си вслух, с выражением. вдумчиво, на предмет почему так сделано.)

anonymous
()
Ответ на: комментарий от RazrFalcon

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

Int0l ★★
()
Ответ на: комментарий от Int0l

лучше
более эффективна

Где эти аксиомы описаны? Нигде? Значит вкусовщина.

удобнее тестировать

Это тут при чём? В том же Rust такой проблемы нет, ибо он умеет тестировать приватные реализации, в отличии от.

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

Так мы сравниваем кресты и си при чем тут проблемы раста?
ООП кстати тоже вкусовщина, т.к. нету точного определения стандарта ООП.

Int0l ★★
()
Последнее исправление: Int0l (всего исправлений: 2)
Ответ на: комментарий от Int0l

т.к. нету точного определения стандарта ООП.

стандарта нету, определения есть. например такое: «программирование на основе повторно используемых компонент, являющихся first class objects с cистемой типов, допускающей наследование, полиморфизм типов и методов, инкапсуляцию поведения».

ИЧСХ, важны не мантры «наследование, инкапсуляция, полиморфизм» ибо это понимание объектной модели в стиле Simula 67 (или C++). и не «акторы и посылка сообщений, все объекты это first class objects» ибо это заморочки объектной модели SmallTalk. и не «исполняемый псевдокод с контрактами и предусловиями, постусловиями, инвариантами, перекрытием свойств методами и наборот, частично реализованными deferred классами, ковариантностью like Current, решёткой объектных типов с ANY и NONE, исключениями с рестартами, SCOOP» — ибо это заморочки Eiffel. и не «let over lambda» схемы и не «мультиметоды и метаобъектный протокол» CLOS. и не «сокрытие данных и расширяемые записи + модули» оберона или модулы3. или там модули в стиле ML, с символами, сигнатурами и функторами (и функциями высшего порядка, функторами как интерфейсами модулей-компонент-«объектов»).

и даже не «операционная семантика ООП с точки зрения теории типов» by Luca Cardelli (хотя у этого вот определения вполне успешные шансы на универсальность, ога).

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

из этих выскоуровневых тоже есть сверхвысокоуровневые (типа полиморфизма типов наподобие тайпклассов, откуда всё и следует согласно Луке Карделли, или вот интерфейсов композабельных компонент в стиле частично реализованных, deffered классов или там миксинов или трейтов ).

и если низкоуровневые реализуют рантайм-обёртку объектной модели, высокоуровневые — её цели, то эти сверхвысокоуровневые — её концептуальные идеи, не зависящие от конкретного языка и «понимания ООП в этом ЯП».

anonymous
()
Ответ на: комментарий от RazrFalcon

инкапсуляция в си есть — это например static методы, неэкспортированные наружу (в хедерах их тоже нет) и колбеки через указатели на них.

такая «let over lambda» для сишников.

anonymous
()
Ответ на: комментарий от RazrFalcon

полиморфизм в си тоже есть — это полиморфизм указателей и формальная возможность присвоить указателю типа первого объекта указатель другого.

другое дело, что при этом конечно вся корректность системы типов (полиморфное присваивание предку потомков, запрет другого полиморфизма типов, в идеале — до эйфелевской «решётки типов» с ANY и NONE) реализуется либо никак, либо обёрткой из костылей, макросов и колбеков в стиле GObject.

здесь, конечно вместо дыры в абстракции системы типов нормальный «компилятор в ООП» должен выдать #error «Illegal type coercion» или как-то так.

anonymous
()
Ответ на: комментарий от anonymous

точнее так: если в лиспах объекты это «let over lambda», то в сишке объекты-модули-компоненты это «lamda-callback over let».

anonymous
()

Сила C как раз таки в отсутствии ООП по дефолту. ООП усложняет читабельность кода как для человека так и для компилятора. Без него можно писать более простой и ясный код.

saahriktu ★★★★★
()
Ответ на: комментарий от Int0l

при этом ООП тоже само по себе низкоуровневый механизьм.

механизм реализации КОП (компонентно-ориентированного программирования) и компонентной среды (определение).

здесь в этом определении важно, что оно расширяется независимо от.

то есть: virtual метод в C++ , который нужно писать заранее в клиенте — плохо, вывод виртуальный/невиртуальный и динамическая типизация/диспетчеризация или статическая компилятором как в Eiffel — хорошо, частично реализованный класс типа deffered в Eiffel, перекрытия функции-геттера атрибутом-свойством в потомке — отлично,

трейты и миксины, функции высшего порядка для интерфейсов или протоколы/категории в ObjC или метаобъектный протокол и рефлексия — вообще замечательно.

anonymous
()
Ответ на: комментарий от saahriktu

вопрос в количестве мест изменений, и их характере. что проще: переопределить 10500 структур или 10500 прототипов функций? зависит от вида изменения.

например был такой Михаил Горбунов-Посадов, написал книжку «расширяемые программы». там он изобрёл «гнёзда расширения» — механизм вроде аспектов, который размножал бы изменения куда нужно.

ну или вспоминаем старого доброго Д. Кнута с WEB и «грамотным программированием» и Emacs org-mode babel, weave/tangle.

anonymous
()
Ответ на: комментарий от anonymous

То, что человек может идти на север, не отменяет того, что он может захотеть пойти или на юг, или на восток, или на запад, или ещё куда-нибудь. Так и здесь. Это уже вопросы архитектуры и кривизны рук кодера. И актуальность этих вопросов возрастает только с увеличением сложности задачи. Там где один кодер обойдётся массивами в кол-ве N штук 2-й кодер начнёт городить кучу сложных структур, а 3-й начнёт продумывать классы, методы и т.д. Так вот, C больше подходит либо для манипулирования отдельными данными, либо для не очень сложных задач структурного программирования. И в этих случаях можно получить более качественный и читабельный код. Ну, а для более сложных случаев есть ООП и C++.

saahriktu ★★★★★
()
Ответ на: комментарий от RazrFalcon

В том же Rust такой проблемы нет, ибо он умеет тестировать приватные реализации, в отличии от.

Справедливости ради, тестировать извне «приватные реализации» всё так же нельзя.

DarkEld3r ★★★★★
()
Ответ на: комментарий от anonymous

полиморфизм в си тоже есть — это полиморфизм указателей и формальная возможность присвоить указателю типа первого объекта указатель другого.

другое дело, что при этом конечно вся корректность системы типов (полиморфное присваивание предку потомков, запрет другого полиморфизма типов, в идеале — до эйфелевской «решётки типов» с ANY и NONE) реализуется либо никак, либо обёрткой из костылей, макросов и колбеков в стиле GObject.

http://www.robertgamble.net/2012/01/c11-generic-selections.html

Freyr69 ★★★
()
Ответ на: комментарий от RazrFalcon

Нельзя. Но это логично.

Логично, но это не то, что Int0l выдвигает как преимущество С.

DarkEld3r ★★★★★
()
Ответ на: комментарий от RisuX3

Лучше не объявлять указатели на функции в структурах, лишнюю память только занимаешь и производительность теряешь.

Это «виртуальная» функция по сути

annulen ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.