What techniques/strategies do people use for build

2019-02-01 15:19发布

问题:

I am especially interested in objects meant to be used from within C, as opposed to implementations of objects that form the core of interpreted languages such as python.

回答1:

I tend to do something like this:

struct foo_ops {
    void (*blah)(struct foo *, ...);
    void (*plugh)(struct foo *, ...);
};
struct foo {
    struct foo_ops *ops;
    /* data fields for foo go here */
};

With these structure definitions, the code implementing foo looks something like this:

static void plugh(struct foo *, ...) { ... }
static void blah(struct foo *, ...) { ... }

static struct foo_ops foo_ops = { blah, plugh };

struct foo *new_foo(...) {
   struct foo *foop = malloc(sizeof(*foop));
   foop->ops = &foo_ops;
   /* fill in rest of *foop */
   return foop;
} 

Then, in code that uses foo:

struct foo *foop = new_foo(...);
foop->ops->blah(foop, ...);
foop->ops->plugh(foop, ...);

This code can be tidied up with macros or inline functions so it looks more C-like

foo_blah(foop, ...);
foo_plugh(foop, ...);

although if you stick with a reasonably short name for the "ops" field, simply writing out the code shown originally isn't particularly verbose.

This technique is entirely adequate for implementing a relatively simple object-based designs in C, but it does not handle more advanced requirements such as explicitly representing classes, and method inheritance. For those, you might need something like GObject (as EFraim mentioned), but I'd suggest making sure you really need the extra features of the more complex frameworks.



回答2:

Your use of the term "objects" is a bit vague, so I'm going to assume you're asking how to use C to achieve certain aspects of Object-Oriented Programming (feel free to correct me on this assumption.)

Method Polymorphism:

Method polymorphism is typically emulated in C using function pointers. For example if I had a struct that I used to represent an image_scaler ( something that takes an image and resizes it to new dimensions ), I could do something like this:

struct image_scaler {
    //member variables
    int (*scale)(int, int, int*);
}

Then, I could make several image scalers as such:

struct image_scaler nn, bilinear;
nn->scale = &nearest_neighbor_scale;
bilinear->scale = &bilinear_scale;

This lets me achieve polymorphic behavior for any function that takes in a image_scaler and uses it's scale method by simply passing it a different image_scaler.

Inheritance

Inheritance is usually achieved as such:

struct base{
   int x;
   int y;
} 

struct derived{
   struct base;
   int z;
}

Now, I'm free to use derived's extra fields, along with getting all the 'inherited' fields of base. Additionally, If you have a function that only takes in a struct base. you can simply cast your struct dervied pointer into a struct base pointer with no consequences



回答3:

Libraries such as GObject.

Basically GObject provides common way to describe opaque values (integers, strings) and objects (by manually describing the interface - as a structure of function pointers, basically correspoinding to a VTable in C++) - more info on the structure can be found in its reference

You would often also hand-implement vtables as in "COM in plain C"



回答4:

As you can see from browsing all the answers, there are libraries, function pointers, means of inheritance, encapsulation, etc., all available (C++ was originally a front-end for C).

However, I have found that a VERY important aspect to software is readability. Have you tried to read code from 10 years ago? As a result, I tend to take the simplest approach when doing things like objects in C.

Ask the following:

  1. Is this for a customer with a deadline (if so, consider OOP)?
  2. Can I use an OOP (often less code, faster to develop, more readable)?
  3. Can I use a library (existing code, existing templates)?
  4. Am I constrained by memory or CPU (for example Arduino)?
  5. Is there another technical reason to use C?
  6. Can I keep my C very simple and readable?
  7. What OOP features do I really need for my project?

I usually revert to something like the GLIB API which allows me to encapsulate my code and provides a very readable interface. If more is needed, I add function pointers for polymorphism.

class_A.h:
  typedef struct _class_A {...} Class_A;
  Class_A* Class_A_new();
  void Class_A_empty();
  ...

#include "class_A.h"
Class_A* my_instance;
my_instance = Class_A_new();
my_instance->Class_A_empty();  // can override using function pointers


回答5:

Look at IJG's implementation. They not only use setjmp/longjmp for exception handling, they have vtables and everything. It is a well written and small enough library for you to get a very good example.



回答6:

Similar to Dale's approach but a bit more of a footgun is how PostgreSQL represents parse tree nodes, expression types, and the like internally. There are default Node and Expr structs, along the lines of

typedef struct {
    NodeTag n;
} Node;

where NodeTag is a typedef for unsigned int, and there's a header file with a bunch of constants describing all the possible node types. Nodes themselves look like this:

typedef struct {
    NodeTag n = FOO_NODE;
    /* other members go here */
} FooNode;

and a FooNode can be cast to a Node with impunity, because of a quirk of C structs: if two structs have identical first members, they can be cast to each other.

Yes, this means that a FooNode can be cast to a BarNode, which you probably don't want to do. If you want proper runtime type-checking, GObject is the way to go, though be prepared to hate life while you're getting the hang of it.

(note: examples from memory, I haven't hacked on the Postgres internals in a while. The developer FAQ has more info.)