C ООП на основе макросов

Я вырос в ООП и недавно углубился в 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 ответ
1

Привлекательность C в том, что он простой, стандартный и производительный. В то время как ваш подход может (?) Не сильно повлиять на производительность, он полностью разрушает первые две концепции.

Для справки, вы конечно не первый, кто пробовал ломать ООП в C. Я несколько отвращаюсь от идеи плотной и сложной макро-магии, не говоря уже о реализации ООП. Мой подход — который, я думаю, вам может быть полезен, хотя ваш вопрос не позволяет продемонстрировать какое-либо конкретное приложение — это

  • Да, использовать ООП; это полезный способ думать о коде
  • Нет, не пытайтесь быть умным и абстрагироваться, используя макросы или иным образом.

ООП вполне возможно с использованием «стандартного C», через

  • Структуры контекста для инкапсуляции переменных-членов
  • Бесплатные функции, принимающие указатели контекстной структуры для представления методов объекта
  • Определение непрозрачной структуры в файлах заголовков для обеспечения разделения задач

Этот шаблон очень распространен в C, очень разборчив и дает чистый код. Передавая код, написанный таким образом, кому-то, кто имеет фундаментальное понимание C, он сможет следить за кодом и вносить свой вклад в него. Передача кода, написанного способом, указанным в вопросе, одному и тому же человеку не даст такого же эффекта. Шаблон, представленный в вопросе, также имеет большую поверхность для ошибок.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *