Object-orientation in C

前端 未结 22 1482
孤城傲影
孤城傲影 2020-11-22 15:26

What would be a set of nifty preprocessor hacks (ANSI C89/ISO C90 compatible) which enable some kind of ugly (but usable) object-orientation in C?

I am familiar with

相关标签:
22条回答
  • 2020-11-22 16:04

    The GNOME desktop for Linux is written in object-oriented C, and it has an object model called "GObject" which supports properties, inheritance, polymorphism, as well as some other goodies like references, event handling (called "signals"), runtime typing, private data, etc.

    It includes preprocessor hacks to do things like typecasting around in the class hierarchy, etc. Here's an example class I wrote for GNOME (things like gchar are typedefs):

    Class Source

    Class Header

    Inside the GObject structure there's a GType integer which is used as a magic number for GLib's dynamic typing system (you can cast the entire struct to a "GType" to find it's type).

    0 讨论(0)
  • 2020-11-22 16:04

    For me object orientation in C should have these features:

    1. Encapsulation and data hiding (can be achieved using structs/opaque pointers)

    2. Inheritance and support for polymorphism (single inheritance can be achieved using structs - make sure the abstract base is not instantiable)

    3. Constructor and destructor functionality (not easy to achieve)

    4. Type checking (at least for user-defined types as C doesn't enforce any)

    5. Reference counting (or something to implement RAII)

    6. Limited support for exception handling (setjmp and longjmp)

    On top of the above it should rely on ANSI/ISO specifications and should not rely on compiler-specific functionality.

    0 讨论(0)
  • 2020-11-22 16:04

    I'm also working on this based on a macro solution. So it is for the bravest only, I guess ;-) But it is quite nice already, and I'm already working on a few projects on top of it. It works so that you first define a separate header file for each class. Like this:

    #define CLASS Point
    #define BUILD_JSON
    
    #define Point__define                            \
        METHOD(Point,public,int,move_up,(int steps)) \
        METHOD(Point,public,void,draw)               \
                                                     \
        VAR(read,int,x,JSON(json_int))               \
        VAR(read,int,y,JSON(json_int))               \
    

    To implement the class, you create a header file for it and a C file where you implement the methods:

    METHOD(Point,public,void,draw)
    {
        printf("point at %d,%d\n", self->x, self->y);
    }
    

    In the header you created for the class, you include other headers you need and define types etc. related to the class. In both the class header and in the C file you include the class specification file (see the first code example) and an X-macro. These X-macros (1,2,3 etc.) will expand the code to the actual class structs and other declarations.

    To inherit a class, #define SUPER supername and add supername__define \ as the first line in the class definition. Both must be there. There is also JSON support, signals, abstract classes, etc.

    To create an object, just use W_NEW(classname, .x=1, .y=2,...). The initialization is based on struct initialization introduced in C11. It works nicely and everything not listed is set to zero.

    To call a method, use W_CALL(o,method)(1,2,3). It looks like a higher order function call but it is just a macro. It expands to ((o)->klass->method(o,1,2,3)) which is a really nice hack.

    See Documentation and the code itself.

    Since the framework needs some boilerplate code, I wrote a Perl script (wobject) that does the job. If you use that, you can just write

    class Point
        public int move_up(int steps)
        public void draw()
        read int x
        read int y
    

    and it will create the class specification file, class header, and a C file, which includes Point_impl.c where you implement the class. It saves quite a lot of work, if you have many simple classes but still everything is in C. wobject is a very simple regular expression based scanner which is easy to adapt to specific needs, or to be rewritten from scratch.

    0 讨论(0)
  • 2020-11-22 16:06

    The open-source Dynace project does exactly that. It's at https://github.com/blakemcbride/Dynace

    0 讨论(0)
  • 2020-11-22 16:07

    I once worked with a C library that was implemented in a way that struck me as quite elegant. They had written, in C, a way to define objects, then inherit from them so that they were as extensible as a C++ object. The basic idea was this:

    • Each object had its own file
    • Public functions and variables are defined in the .h file for an object
    • Private variables and functions were only located in the .c file
    • To "inherit" a new struct is created with the first member of the struct being the object to inherit from

    Inheriting is difficult to describe, but basically it was this:

    struct vehicle {
       int power;
       int weight;
    }
    

    Then in another file:

    struct van {
       struct vehicle base;
       int cubic_size;
    }
    

    Then you could have a van created in memory, and being used by code that only knew about vehicles:

    struct van my_van;
    struct vehicle *something = &my_van;
    vehicle_function( something );
    

    It worked beautifully, and the .h files defined exactly what you should be able to do with each object.

    0 讨论(0)
  • 2020-11-22 16:07

    I'm a bit late to the party here but I like to avoid both macro extremes - too many or too much obfuscates code, but a couple obvious macros can make the OOP code easier to develop and read:

    /*
     * OOP in C
     *
     * gcc -o oop oop.c
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    
    struct obj2d {
        float x;                            // object center x
        float y;                            // object center y
        float (* area)(void *);
    };
    
    #define X(obj)          (obj)->b1.x
    #define Y(obj)          (obj)->b1.y
    #define AREA(obj)       (obj)->b1.area(obj)
    
    void *
    _new_obj2d(int size, void * areafn)
    {
        struct obj2d * x = calloc(1, size);
        x->area = areafn;
        // obj2d constructor code ...
        return x;
    }
    
    // --------------------------------------------------------
    
    struct rectangle {
        struct obj2d b1;        // base class
        float width;
        float height;
        float rotation;
    };
    
    #define WIDTH(obj)      (obj)->width
    #define HEIGHT(obj)     (obj)->height
    
    float rectangle_area(struct rectangle * self)
    {
        return self->width * self->height;
    }
    
    #define NEW_rectangle()  _new_obj2d(sizeof(struct rectangle), rectangle_area)
    
    // --------------------------------------------------------
    
    struct triangle {
        struct obj2d b1;
        // deliberately unfinished to test error messages
    };
    
    #define NEW_triangle()  _new_obj2d(sizeof(struct triangle), triangle_area)
    
    // --------------------------------------------------------
    
    struct circle {
        struct obj2d b1;
        float radius;
    };
    
    #define RADIUS(obj)     (obj)->radius
    
    float circle_area(struct circle * self)
    {
        return M_PI * self->radius * self->radius;
    }
    
    #define NEW_circle()     _new_obj2d(sizeof(struct circle), circle_area)
    
    // --------------------------------------------------------
    
    #define NEW(objname)            (struct objname *) NEW_##objname()
    
    
    int
    main(int ac, char * av[])
    {
        struct rectangle * obj1 = NEW(rectangle);
        struct circle    * obj2 = NEW(circle);
    
        X(obj1) = 1;
        Y(obj1) = 1;
    
        // your decision as to which of these is clearer, but note above that
        // macros also hide the fact that a member is in the base class
    
        WIDTH(obj1)  = 2;
        obj1->height = 3;
    
        printf("obj1 position (%f,%f) area %f\n", X(obj1), Y(obj1), AREA(obj1));
    
        X(obj2) = 10;
        Y(obj2) = 10;
        RADIUS(obj2) = 1.5;
        printf("obj2 position (%f,%f) area %f\n", X(obj2), Y(obj2), AREA(obj2));
    
        // WIDTH(obj2)  = 2;                                // error: struct circle has no member named width
        // struct triangle  * obj3 = NEW(triangle);         // error: triangle_area undefined
    }
    

    I think this has a good balance, and the errors it generates (at least with default gcc 6.3 options) for some of the more likely mistakes are helpful instead of confusing. The whole point is to improve programmer productivity no?

    0 讨论(0)
提交回复
热议问题