codekote
ООП в Си. Разные подходы в реализации.
Занимательная заметка о различных подходах к реализации ООП в Си.
#1
@kafeman описал в своём комметарии на Хабре. Для меня не совсем ясен постфикс Private
, все структуры находятся в глобальной области видимости, методы тоже. Достучаться до члена x
“приватной” структуры Point2DPrivate
можно через point->parent.private->x
. Возможно автор хотел показать общий подход к построению классов/объектов и не собирался описывать сокрытие.
// main.c
#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;
}
Сам же пост, на который @kafeman оставил комментарий, описывает подход сокрытия через возможности спецификатора static
.
Цитата @dehasi
Имея такую возможность, можно разделить public и private данные по разным файлам, а в структуре хранить только указатель на приватные данные. Понадобится две структуры: одна приватная, а вторая с методами для работы и указателем на приватную. Чтобы вызывать функции на объекте, договоримся первым параметром передавать указатель на структуру, которая ее вызывает. Объявим структуру с сеттерами, геттерами и указателем на приватное поле, а также функции, которые будут создавать структуру и удалять.
//point2d.h
typedef struct point2D {
void *prvtPoint2D;
int (*getX) (struct point2D*);
void (*setX)(struct point2D*, int);
//...
} point2D;
point2D* newPoint2D();
void deletePoint2D(point2D*);
Цитата @dehasi
Здесь будет инициализироваться приватное поле и указатели на функции, чтобы с этой структурой можно было работать.
//point2d.c
#include <stdlib.h>
#include "point2d.h"
typedef struct private {
int x;
int y;
} private;
static int getx(struct point2D*p) {
return ((struct private*)(p->prvtPoint2D))->x;
}
static void setx(struct point2D *p, int val) {
((struct private*)(p->prvtPoint2D))->x = val;
}
point2D* newPoint2D() {
point2D* ptr;
ptr = (point2D*) malloc(sizeof(point2D));
ptr -> prvtPoint2D = malloc(sizeof(private));
ptr -> getX = &getx;
ptr -> setX = &setx;
// ....
return ptr;
}
Цитата @dehasi
Теперь, работа с этой структурой, может осуществляться с помощью сеттеров и геттеров.
// main.c
#include <stdio.h>
#include "point2d.h"
int main() {
point2D *point = newPoint2D();
int p = point->getX(point);
point->setX(point, 42);
p = point->getX(point);
printf("p = %d\n", p);
deletePoint2D(point);
return 0;
}
#2
В книге Extreme C автор Amini K. постарался описать всё: инкапсуляцию и сокрытие, типы связей объектов/классов (наследование, агрегация, композиция), а также полиморфизм. И всё это на чистом Си. Автор создал репозиторий, где опубликовал весь исходный код, который был в книге. По разделам можно смотреть здесь:
- Chapter 6: OOP and Encapsulation
- Chapter 7: Composition and Aggregation
- Chapter 8: Inheritance and Polymorphism
Основная идея заключается в разделении облести видимости по файлам. Каждый родительский объект имеет интерфейс только к дочернему. Ниже отрывки из кода (не полностью).
// class_and_methods.c — описываются данные и методы
typedef int bool_t;
// Декларация структуры
typedef struct {
size_t size;
int* items;
} list_t;
//...
// Приватный метод с префиксом __
bool_t __list_is_full(list_t* list) {
return (list->size == MAX_SIZE);
}
// Публичный метод
size_t list_size(list_t* list) {
return list->size;
}
//...
// public.h - заголовочный файл раскрывает вышестоящему только те данные и методы, которые в нём описаны. В данном случае main.c имеет доступ только к публичным методам.
...
struct list_t; // предватительная декларация
// Публичные методы
int list_add(struct list_t*, int);
size_t list_size(struct list_t*);
//...
// main.c
#include <public.h>
int main(int argc, char** argv) {
//...
list_add(list1, 4);
list_size(source);
//...
}
Сохраню здесь, буду периодически подглядывать.