Object-orientation in C

2019-01-01 01:27发布

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 a few different object-oriented languages, so please don't respond with answers like "Learn C++!". I have read "Object-Oriented Programming With ANSI C" (beware: PDF format) and several other interesting solutions, but I'm mostly interested in yours :-)!


See also Can you write object oriented code in C?

标签: c oop object
18条回答
余生无你
2楼-- · 2019-01-01 02:01

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.

查看更多
其实,你不懂
3楼-- · 2019-01-01 02:02

ffmpeg (a toolkit for video processing) is written in straight C (and assembly language), but using an object-oriented style. It's full of structs with function pointers. There are a set of factory functions that initialize the structs with the appropriate "method" pointers.

查看更多
ら面具成の殇う
4楼-- · 2019-01-01 02:02

My recommendation: keep it simple. One of the biggest issues I have is maintaining older software (sometimes over 10 years old). If the code is not simple, it can be difficult. Yes, one can write very useful OOP with polymorphism in C, but it can be difficult to read.

I prefer simple objects that encapsulate some well-defined functionality. A great example of this is GLIB2, for example a hash table:

GHastTable* my_hash = g_hash_table_new(g_str_hash, g_str_equal);
int size = g_hash_table_size(my_hash);
...

g_hash_table_remove(my_hash, some_key);

The keys are:

  1. Simple architecture and design pattern
  2. Achieves basic OOP encapsulation.
  3. Easy to implement, read, understand, and maintain
查看更多
人间绝色
5楼-- · 2019-01-01 02:03

@Adam Rosenfield has a very good explanation of how to achieve OOP with C

Besides, I would recommend you to read

1) pjsip

A very good C library for VoIP. You can learn how it achieves OOP though structs and function pointer tables

2) iOS Runtime

Learn how iOS Runtime powers Objective C. It achieves OOP through isa pointer, meta class

查看更多
萌妹纸的霸气范
6楼-- · 2019-01-01 02:06

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.

查看更多
伤终究还是伤i
7楼-- · 2019-01-01 02:08

I would advise against preprocessor (ab)use to try and make C syntax more like that of another more object-oriented language. At the most basic level, you just use plain structs as objects and pass them around by pointers:

struct monkey
{
    float age;
    bool is_male;
    int happiness;
};

void monkey_dance(struct monkey *monkey)
{
    /* do a little dance */
}

To get things like inheritance and polymorphism, you have to work a little harder. You can do manual inheritance by having the first member of a structure be an instance of the superclass, and then you can cast around pointers to base and derived classes freely:

struct base
{
    /* base class members */
};

struct derived
{
    struct base super;
    /* derived class members */
};

struct derived d;
struct base *base_ptr = (struct base *)&d;  // upcast
struct derived *derived_ptr = (struct derived *)base_ptr;  // downcast

To get polymorphism (i.e. virtual functions), you use function pointers, and optionally function pointer tables, also known as virtual tables or vtables:

struct base;
struct base_vtable
{
    void (*dance)(struct base *);
    void (*jump)(struct base *, int how_high);
};

struct base
{
    struct base_vtable *vtable;
    /* base members */
};

void base_dance(struct base *b)
{
    b->vtable->dance(b);
}

void base_jump(struct base *b, int how_high)
{
    b->vtable->jump(b, how_high);
}

struct derived1
{
    struct base super;
    /* derived1 members */
};

void derived1_dance(struct derived1 *d)
{
    /* implementation of derived1's dance function */
}

void derived1_jump(struct derived1 *d, int how_high)
{
    /* implementation of derived 1's jump function */
}

/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
    &derived1_dance, /* you might get a warning here about incompatible pointer types */
    &derived1_jump   /* you can ignore it, or perform a cast to get rid of it */
};

void derived1_init(struct derived1 *d)
{
    d->super.vtable = &derived1_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

struct derived2
{
    struct base super;
    /* derived2 members */
};

void derived2_dance(struct derived2 *d)
{
    /* implementation of derived2's dance function */
}

void derived2_jump(struct derived2 *d, int how_high)
{
    /* implementation of derived2's jump function */
}

struct base_vtable derived2_vtable =
{
   &derived2_dance,
   &derived2_jump
};

void derived2_init(struct derived2 *d)
{
    d->super.vtable = &derived2_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

int main(void)
{
    /* OK!  We're done with our declarations, now we can finally do some
       polymorphism in C */
    struct derived1 d1;
    derived1_init(&d1);

    struct derived2 d2;
    derived2_init(&d2);

    struct base *b1_ptr = (struct base *)&d1;
    struct base *b2_ptr = (struct base *)&d2;

    base_dance(b1_ptr);  /* calls derived1_dance */
    base_dance(b2_ptr);  /* calls derived2_dance */

    base_jump(b1_ptr, 42);  /* calls derived1_jump */
    base_jump(b2_ptr, 42);  /* calls derived2_jump */

    return 0;
}

And that's how you do polymorphism in C. It ain't pretty, but it does the job. There are some sticky issues involving pointer casts between base and derived classes, which are safe as long as the base class is the first member of the derived class. Multiple inheritance is much harder - in that case, in order to case between base classes other than the first, you need to manually adjust your pointers based on the proper offsets, which is really tricky and error-prone.

Another (tricky) thing you can do is change the dynamic type of an object at runtime! You just reassign it a new vtable pointer. You can even selectively change some of the virtual functions while keeping others, creating new hybrid types. Just be careful to create a new vtable instead of modifying the global vtable, otherwise you'll accidentally affect all objects of a given type.

查看更多
登录 后发表回答