Comments 26
Вообще-то, был такой человек по фамилии Страуструп, он давным давно схожие идеи продвигал. Не помню толком чем у него там кончилось.
Какой смысл писать на C и хранить ссылки на методы в полях структуры? Так даже в языках высокого уровня не всегда делается, чтобы не копировать в сто миллионов объектов с двумя полями ссылки на 10 методов.
А не надо хранить в объекте указатели на функции. Надо положить все указатели в отдельную структуру (размещённую в статической памяти) и хранить в объектах указатель на эту структуру. Примерно так это и сделано в C++, и называется это vtable.
и по-моему именно таким образом в ядре linux реализованы «виртуальные методы» в С.
Всё это уже давно написано, причём существенно лучше: Object-Oriented Programming With ANSI-C
Пример того, как подобные идеи выглядят на практике — GObject из GLib.
Пример того, как подобные идеи выглядят на практике — GObject из GLib.
Дочитал до середины, стало плохо. «Что только не выдумают, лишь бы не писать на C++». Нет, ну техники хорошие, но от осознания что все это может делать компилятор (с ровно такой же эффективностью!), становится не по себе.
upd. ну может понадобиться для тех проектов, в которые нельзя тащить плюсы ну никак. Например, linux kernel.
upd. ну может понадобиться для тех проектов, в которые нельзя тащить плюсы ну никак. Например, linux kernel.
В Linux kernel есть kobject для этого. У них в коде ядра на C много разных парадигм и абстракций, не только объектно-ориентированная.
А вот не скажите! Как только влезаешь в reverse-engineering, так сразу всё «всплывает». И безымянные структуры внутри основной, и передача предка по указателю…
И вообще, MS-ная технология COM рулит, когда есть только отладочные символы и дизассемблер! С становится настоящим высокоуровневым дизассемблером. А С++ — синтаксическим сахаром над ним. Ну и косяки разработчиков заодно тоже выявляются (MS в OE/WinMail заюзали тот же GUID для похожего интерфейса, но с уже другими параметрами (64 vs 32 бита шириной)
И вообще, MS-ная технология COM рулит, когда есть только отладочные символы и дизассемблер! С становится настоящим высокоуровневым дизассемблером. А С++ — синтаксическим сахаром над ним. Ну и косяки разработчиков заодно тоже выявляются (MS в OE/WinMail заюзали тот же GUID для похожего интерфейса, но с уже другими параметрами (64 vs 32 бита шириной)
Учитывая то, что GLib это основа современного Linux freedesktop мы получаем, что весь gui и окологуевый софт включая фрэймворки типа GStreamer написаны используя те же идеи, вот это поворот.
Структуры при наследовании лучше все-таки именовать, будет удобнее работать с ними в дальнейшем. Про указатели на методы вам уже сказали выше. Конструктор и деструктор объекта лучше вынести в отдельные функции — опять же, сильно поможет при наследовании.
Вот переделанный пример из вашей статьи:
Вот переделанный пример из вашей статьи:
#include <stdio.h>
#include <stdlib.h>
typedef struct _Point2DPrivate {
int x;
int y;
} Point2DPrivate;
typedef struct _Point2D {
Point2DPrivate *private;
} Point2D;
void Point2D_Constructor(Point2D *point) {
point->private = malloc(sizeof(Point2DPrivate));
point->private->x = 0;
point->private->y = 0;
}
void Point2D_Destructor(Point2D *point) {
free(point->private);
}
void Point2D_SetX(Point2D *point, int x) {
point->private->x = x;
}
int Point2D_GetX(Point2D *point) {
return point->private->x;
}
void Point2D_SetY(Point2D *point, int y) {
point->private->y = y;
}
int Point2D_GetY(Point2D *point) {
return point->private->y;
}
Point2D *Point2D_New(void) {
Point2D *point = malloc(sizeof(Point2D));
Point2D_Constructor(point); /* <-- Вызываем конструктор */
return point;
}
void Point2D_Delete(Point2D *point) {
Point2D_Destructor(point); /* <-- Вызываем деструктор */
free(point);
}
typedef struct _Point3DPrivate {
int z;
} Point3DPrivate;
typedef struct _Point3D {
Point2D parent;
Point3DPrivate *private;
} Point3D;
void Point3D_Constructor(Point3D *point) {
Point2D_Constructor(&point->parent); /* <-- Вызываем родительский конструктор! */
point->private = malloc(sizeof(Point3DPrivate));
point->private->z = 0;
}
void Point3D_Destructor(Point3D *point) {
Point2D_Destructor(&point->parent); /* <-- Вызываем родительский деструктор! */
free(point->private);
}
void Point3D_SetZ(Point3D *point, int z) {
point->private->z = z;
}
int Point3D_GetZ(Point3D *point) {
return point->private->z;
}
Point3D *Point3D_New(void) {
Point3D *point = malloc(sizeof(Point3D));
Point3D_Constructor(point); /* <-- Вызываем конструктор */
return point;
}
void Point3D_Delete(Point3D *point) {
Point3D_Destructor(point); /* <-- Вызываем деструктор */
free(point);
}
int main(int argc, char **argv) {
/* Создаем экземпляр класса Point3D */
Point3D *point = Point3D_New();
/* Устанавливаем x и y координаты */
Point2D_SetX((Point2D*)point, 10);
Point2D_SetY((Point2D*)point, 15);
/* Теперь z координата */
Point3D_SetZ(point, 20);
/* Должно вывести: x = 10, y = 15, z = 20 */
printf("x = %d, y = %d, z = %d\n",
Point2D_GetX((Point2D*)point),
Point2D_GetY((Point2D*)point),
Point3D_GetZ(point));
/* Удаляем объект */
Point3D_Delete(point);
return 0;
}
Компилятор выдает ошибкуОшибку выдает компоновщик, т. е. вы сначала 2 часа будете ждать пока все скомпилируется, а потом, при компоновке, вам сообщат, что вы оказывается «обратились к приватной функции» (причем шифрованным сообщением) и зря ждали 2 часа пока все скомпилируется.
Тем не менее, оставить один указатель лучше, чем хранить все данные в паблик структуре.Не очевидное утверждение, особенно, на таком примере, в котором вы тут же в дополнение к закрытым данным делаете set и get с тривиальной функциональностью, это явно не лучше, чем просто оставить x и y доступными снаружи.
Кроме того, инкапсуляция может осуществляется как соглашение между программистами без всякой поддержки со стороны компилятора. Например, все поля, имена которых начинаются с нижнего подчеркивания лучше не трогать снаружи какого-то модуля, а если уж трогаете, то на свой страх и риск.
Инкапсуляция
подразумевает скрытие данных от разработчика. В ООП языках мы обычно скрываем поля класса, а для доступа к ним пишем сеттеры и геттеры.
Это неверно. Инкапсуляция есть скрыть деталей реализации от пользователя.
Кхм, я не в тему статьи, но мне интересно, почему вдруг ввели новые фичи со странным стилем именования:
_Generic
, _Bool
, _Thread_local
?По поводу плашек к статье — насколько я знаю, не такое уж это ненормальное программирование. Описанный подход имитации работы с объектами на С (только без извращений с имитацией наследования) используется в случаи если нужно описать интерфейс бинарно совместимой библиотеки, с чем у С++, увы, врождённые проблемы. Использование API на языке С позволяет избежать издержек при вызове функционала классов, который в противном случаи пришлось бы вызывать через чисто виртуальные функции (описано, например, вот тут; работает, вроде, за счёт вхождения стандарта описания таблиц виртуальных функций в ABI).
Надеюсь, знающие люди исправят меня, если я что-то не так понимаю — не совсем уверен в том, что понимаю всё это правильно до конца…
П.С.: Код в пример чем-то андроидовское NDK и эпловый Core Foundation напомнил… Что логично, впрочем — это и есть ООП API, которое имитируется средствами С.
Надеюсь, знающие люди исправят меня, если я что-то не так понимаю — не совсем уверен в том, что понимаю всё это правильно до конца…
П.С.: Код в пример чем-то андроидовское NDK и эпловый Core Foundation напомнил… Что логично, впрочем — это и есть ООП API, которое имитируется средствами С.
Взгляните, кстати, как написан FreeRADIUS. Там почти ООП на голом C.
>Инкапсуляция подразумевает скрытие данных от разработчика.
Не от, а для.
Инкапсуляция позволяет скрывать несущественные для данного уровня абстракции детали реализации, но полный запрет доступа к ним к инкапсуляции относится слабо. Просто некоторые ЯП, не будем показывать пальцем, поставили знак равенства между такой классной штукой как инкапсуляция и не такой классной (хотя, иногда, и полезной) как сокрытие.
Не от, а для.
Инкапсуляция позволяет скрывать несущественные для данного уровня абстракции детали реализации, но полный запрет доступа к ним к инкапсуляции относится слабо. Просто некоторые ЯП, не будем показывать пальцем, поставили знак равенства между такой классной штукой как инкапсуляция и не такой классной (хотя, иногда, и полезной) как сокрытие.
Кстати говоря, в нашей компании основной продукт писался на С. Исторически так сложилось. Только вот наши разработчики умудрялись все это делать с помощью компилятора ANSI C, без всяких новомодных штучек. На вопрос «зачем все это?» ответ примерно такой — «Ну блин, мы же так долго парились, да и на С++ переписывать неохота»
Можно подумать, что виртуальные функции и наследование — это самая важная фича C++.
Не знаю, как обстоят дела у остальных, но лично мне нужно заводить классы с виртуальными методами довольно редко. Да и коллеги подобное делают весьма нечасто.
Удобное детерминированное управление ресурсами, обобщённое программирование, более строгая (чем в C) система типизации, большая свобода при построении zero-cost абстракций — вот особенности C++, которые меня радуют больше всего.
Когда я пишу на C, больше всего я тоскую по деструкторам.
Не знаю, как обстоят дела у остальных, но лично мне нужно заводить классы с виртуальными методами довольно редко. Да и коллеги подобное делают весьма нечасто.
Удобное детерминированное управление ресурсами, обобщённое программирование, более строгая (чем в C) система типизации, большая свобода при построении zero-cost абстракций — вот особенности C++, которые меня радуют больше всего.
Когда я пишу на C, больше всего я тоскую по деструкторам.
ООП, это не поддержка тем или иным языком, это идиология разработки. И даже если язык явно не поддерживает это красивым синтаксисом это можно сделать без проблем. И это в первую очередь именно методология разработки, а не синтаксис на конкретном языке программирования. Если взять и посмотреть на код Кармака, он ООП, но на C и без подобных костылей описанных в статье. Да и мне приходилось работать с большой кучей библиотек с идиологией ООП, на C. Но всё же хранить методы в объекте, это только если замена виртуальности и ничего более.
Sign up to leave a comment.
Псевдо ООП в C