Я вырос в ООП и недавно углубился в C и встроенные системы.
В качестве упражнения я хотел посмотреть, смогу ли я обернуть некоторые концепции ООП в заголовок макроса.
Он работает так, как я задумал, довольно прост в использовании и хорошо разбирается, если вы знаете терминологию / концепции ООП.
Хотел бы получить обратную связь, чтобы узнать, делаю ли я что-нибудь глупое.
На данный момент это только выделение кучи. Я буду обновлять в будущем объекты структуры на основе стека, но для реализации этого кодировщику требуется только написать 1 дополнительную функцию.
Это> C / GNU99 и требует P99 для вариативных макросов (я еще не скопировал эти маленькие кусочки).
eOOPc.h
#ifndef OOP_MAIN_H
#define OOP_MAIN_H
#ifndef _STDLIB_H
#include <stdlib.h>
#endif
#ifndef P99_IF_H_
#include "p99/p99_if.h"
#endif
/**
* grab an interface definition by calling it's macro.
* @param i Interface name
*/
#define eIMPLEMENTS(i) eINTERFACE_##i()
/**
* add a parent class struct to gain access to it's public methods
* @param p Struct name
* @param n Property name
*/
#define eEXTENDS(p,n) struct p n
/**
* helper macro to denote that this parent is upcastable (this macro must be first element of containing
* struct for this to be true)
* @param p Struct name
* @param n Property name
*/
#define eDIR_EXTENDS(p,n) eEXTENDS(p, n)
/**
* Instantiate an object 'o*' of type 'c' by using function 'c_instatiate()'
* @param <classtype_t> c
* @param var o Object variable name
* @param ... any further arguments
*/
#define eNEW_INS(c,o, ...) P99_IF_EMPTY(__VA_ARGS__) (c##_instantiate(o)) (c##_instantiate(o, __VA_ARGS__))
/**
* Call allocation method and imediately fire instatiation function for heap object
* @param <classtype_t> c
* @param var o Object variable name
* @param ... any further arguments
*/
#define eNEW(c,o, ...) struct c*o = (struct c *)malloc(sizeof(struct c)); eNEW_INS(c,o, __VA_ARGS__)
/**
* public property DECLARATION
* @param t Type
* @param p Property name
*/
#define ePROP_DEC(t, p) t p
/**
* public property DEFINITION
* @param p Property name
* @param v Value
*/
#define ePROP_DEF(p, v) self->p = v
/**
* private property PUBLIC function-pointer DECLARATIONS for public struct
* @param t Type
* @param p Property name
* @param m get/set/getset
*/
#define ePRIV_PROP_DEC_get(t,p) t (*get_##p)(void * eOBJ)
#define ePRIV_PROP_DEC_set(t,p) void (*set_##p)(void * eOBJ, t v)
#define ePRIV_PROP_DEC_getset(t,p) ePRIV_PROP_DEC_get(t,p); ePRIV_PROP_DEC_set(t,p)
#define ePRIV_PROP_DEC_PUB(t, p, m) ePRIV_PROP_DEC_##m(t,p)
/**
* private property PRIVATE function-pointer DECLARATIONS for private struct
* @param t Type
* @param p Property name
* @param m get/set/getset
*/
#define ePRIV_PROP_DEC_PRIV(t, p, m) ePROP_DEC(t,p); ePRIV_PROP_DEC_##m(t,p)
/**
* private property PUBLIC function DECLARATIONS
* @param c Type
* @param t Function return type
* @param p Property name
* @param m get/set/getset
*/
#define ePRIV_PROP_FUNC_DEC_get(c, t, p) t c##_get_##p(void * eOBJ);
#define ePRIV_PROP_FUNC_DEC_set(c, t, p) void c##_set_##p(void * eOBJ, t v );
#define ePRIV_PROP_FUNC_DEC_getset(c, t, p) ePRIV_PROP_FUNC_DEC_get(c, t, p) ePRIV_PROP_FUNC_DEC_set(c, t, p)
#define ePRIV_PROP_FUNC_DEC(c, t, p, m) ePRIV_PROP_FUNC_DEC_##m(c, t, p)
/**
* private property function ALLOCATIONS for object function pointers in instantiate
* @param c Type
* @param p Property name
* @param v Property value
* @param m get/set/getset
*/
#define ePRIV_PROP_DEF_get(c, p) self->get_##p = &c##_get_##p
#define ePRIV_PROP_DEF_set(c, p) self->set_##p = &c##_set_##p
#define ePRIV_PROP_DEF_getset(c, p) ePRIV_PROP_DEF_get(c, p); ePRIV_PROP_DEF_set(c, p)
#define ePRIV_PROP_DEF(c, p, v, m) self->p = v; ePRIV_PROP_DEF_##m(c, p)
/**
* private property PUBLIC get/set function DEFINITIONS
* @param c Type
* @param t Function return type
* @param p Property name
* @param m get/set/getset
*/
#define ePRIV_PROP_FUNC_DEF_get(c, t, p) t c##_get_##p(void * eOBJ){ eSELF(c); return self->p; }
#define ePRIV_PROP_FUNC_DEF_set(c, t, p) void c##_set_##p(void * eOBJ, t v ){ eSELF(c); self->p = v; }
#define ePRIV_PROP_FUNC_DEF_getset(c, t, p) ePRIV_PROP_FUNC_DEF_get(c, t, p) ePRIV_PROP_FUNC_DEF_set(c, t, p)
#define ePRIV_PROP_FUNC_DEF(c, t, p, m) ePRIV_PROP_FUNC_DEF_##m(c, t, p)
/**
* Cast "self" back into the class type
* @param <classtype_t> c
*/
#define eSELF(c) c * self = (c*)eOBJ
/**
* Get the value of a protected variable 'p' within object 'o'
* Prints to stderr if property is private
* @param var o Object
* @param var p Object property
*/
#define eGET(o, p) o->get_##p(o)
/**
* Set the value of a protected variable 'x' within object 'o'
* Prints to stderr if property is private
* @param var o Object
* @param var p Object property
* @param var v The new value
*/
#define eSET(o, p, v) o->set_##p(o, v)
/**
* Method call wrapper that passes object as first argument for use of eSELF()
* @param var o Object
* @param var m The method
* @param ... Other args
*/
#define eMETH(o, m, ...) P99_IF_EMPTY(__VA_ARGS__) ((*o->m)(o)) ((*o->m)(o, __VA_ARGS__))
/**
* Free memory on heap for object
* @param var o Object variable name
*/
#define eDESTROY(o) free(o); o = ((void*)0)
/**
* Free memory on heap for object by calling defined function to allow further actions
* such as destroying string / struct members within object
* @param <classtype_t> c
* @param var o Object variable name
*/
#define eDESTROY_M(c, o) c##_heap_destruct(o); o = ((void*)0)
#endif //OOP_MAIN_H
С некоторыми файлами примеров:
class.h общедоступный заголовочный файл
#ifndef OOP_CLASS_H
#define OOP_CLASS_H
//interface definitions can contain expressly written variables,
//other e@ macros etc as needed.
#define eINTERFACE_interface()
int poop;
int shmoop
struct parent{
int pprop1;
int pprop2;
};
//PUBLIC DEFINITION AND METHODS
struct Class_t{
//parent for upcasts
eDIR_EXTENDS(parent, parent);
//interface
eIMPLEMENTS(interface);
//define a public property
ePROP_DEC(int, prop1);
//define function pointers for get, set or both for private property
ePRIV_PROP_DEC_PUB(int, prop2, get);
//public method function pointer
int (*method1)(void * eOBJ);
};
//public class function declarations
//get and/or set PUBLIC function declarations
ePRIV_PROP_FUNC_DEC(Class_t, int, prop2, get)
//other defined public methods declarations
int Class_t_method1(void * eOBJ);
//always need an instantiate. Void pointer so function can cast back to struct type pointer
//this means we don't get conflicting type errors
void Class_t_instantiate(void * eOBJ);
#endif //OOP_CLASS_H
class.c Частный исходный код и определения
//need OOP macros
#include "eOOPc.h"
//include public declaration
#include "class.h"
//PRIVATE DECLARAION
typedef struct{
eDIR_EXTENDS(parent, parent);
//interface
eIMPLEMENTS(interface);
//matching public prop declaration
ePROP_DEC(int, prop1);
//private property declaration + PUBLIC function pointer declarations
ePRIV_PROP_DEC_PRIV(int, prop2, get);
//public method function pointer
int (*method1)(void * eOBJ);
//private method function pointer
int (*method2)(void * eOBJ);
} Class_t;
//PRIVATE METHODS
int Class_t_method2(void * eOBJ){
eSELF(Class_t);
return self->prop1;
}
//PUBLIC METHOD DEFINITIONS TO OVERRIDE DECLARATIONS
//private property PUBLIC get/set method definitions
ePRIV_PROP_FUNC_DEF(Class_t, int, prop2, get)
//other public method definitions
int Class_t_method1(void * eOBJ){
eSELF(Class_t);
return 4;
}
void Class_t_instantiate(void * eOBJ){
eSELF(Class_t);
ePROP_DEF(prop1, 3);
ePRIV_PROP_DEF(Class_t, prop2, 4, get);
self->method1 = &Class_t_method1;
self->method2 = &Class_t_method2;
}
main.c Пример
Обратите внимание, что у него есть доступ только к файлу class.h — дальнейшие методы и свойства, определенные в class.c, являются частными и недоступными.
#include "eOOPc.h"
#include "class.h"
int main (void){
//instantiate object (i.e. new)
eNEW(Class_t, object);
int i = eMETH(object, method1);
return 0;
}
1 ответ
Привлекательность C в том, что он простой, стандартный и производительный. В то время как ваш подход может (?) Не сильно повлиять на производительность, он полностью разрушает первые две концепции.
Для справки, вы конечно не первый, кто пробовал ломать ООП в C. Я несколько отвращаюсь от идеи плотной и сложной макро-магии, не говоря уже о реализации ООП. Мой подход — который, я думаю, вам может быть полезен, хотя ваш вопрос не позволяет продемонстрировать какое-либо конкретное приложение — это
- Да, использовать ООП; это полезный способ думать о коде
- Нет, не пытайтесь быть умным и абстрагироваться, используя макросы или иным образом.
ООП вполне возможно с использованием «стандартного C», через
- Структуры контекста для инкапсуляции переменных-членов
- Бесплатные функции, принимающие указатели контекстной структуры для представления методов объекта
- Определение непрозрачной структуры в файлах заголовков для обеспечения разделения задач
Этот шаблон очень распространен в C, очень разборчив и дает чистый код. Передавая код, написанный таким образом, кому-то, кто имеет фундаментальное понимание C, он сможет следить за кодом и вносить свой вклад в него. Передача кода, написанного способом, указанным в вопросе, одному и тому же человеку не даст такого же эффекта. Шаблон, представленный в вопросе, также имеет большую поверхность для ошибок.
