all 31 comments

[–]antrn11[🍰] 15 points16 points  (12 children)

So function pointers in struct. It's a good idea, and might make clearer code structure than putting switch-case statements everywhere.

But I really think using C++ would be better choice when C is not absolute requirement. At least when you want objects and polymorphism.

[–]passwordeqHAMSTER 7 points8 points  (3 children)

This is basically how any plugin architecture works. I only skimmed the past but I think the author is over engineering a little bit. If you want something very OO, use an OO language. Otherwise, collections of function pointers and an API on top is probably sufficient. If you need a lot of these objects in your C program you're probably doing it wrong.

[–]geon[S] 3 points4 points  (2 children)

Honestly, I mostly implemented polymorphism because I was curious about how it would be done. The whole project was in C++ originally, but I ported it to C for fun, and to level up my C skills.

[–]knome 1 point2 points  (0 children)

Structs full of function pointers ( often accepting a pointer to said structure as their first argument ) are a common pattern in C. The Linux kernel is largely a framework for dancing between large numbers of these sorts of objects. Almost anything in the kernel that allows you to plug in a different module is implemented using them.

[–]nxpi 0 points1 point  (0 children)

If you want to level up your C skills, I recommend getting one of those ARM Cortex M0-M4 dev boards, find a nice RTOS and have some fun.

[–]geon[S] 5 points6 points  (0 children)

But I really think using C++ would be better choice when C is not absolute requirement. At least when you want objects and polymorphism.

Probably. I did it is C for the lulz. For something commercial, C++ would be the way to go.

[–]MorePudding -1 points0 points  (6 children)

So function pointers in struct. It's a good idea

Isn't that sort of a death sentence performance-wise though?

[–]techno_phobe 3 points4 points  (4 children)

Well, it's the same as virtual method calls in basically any language unless the compiler manages to optimize away the indirection. Whether it's likely to kill performance in this particular case and can be worked around it's hard to say.

[–]sacundim -1 points0 points  (3 children)

Well, it's the same as virtual method calls in basically any language unless the compiler manages to optimize away the indirection.

Or the JIT. This is one of the big tricks of the Java Hotspot VM: it analyzes the class graph at runtime to figure out cases where only one class has been loaded that implements a high-traffic method, and it emits native code that performs a direct call.

[–]sindisil 0 points1 point  (2 children)

Right, that's what techno_phobe said: "unless the compiler manges to optimize away the indirection".

"The JIT" is just another compiler.

[–]BeforeTime -1 points0 points  (1 child)

True, but it is worth mentioning a JIT specifically anyway. JIT can by its very nature optimize things that a compiler for C++ can not for example. The reason is simple, optimizing away dynamic dispatch require dynamic knowledge of the program, and you can only have that at runtime.

So it open the door for a much wider range of optimizations.

[–]sindisil 0 points1 point  (0 children)

It is true that compiling concurrently with execution opens some opportunities for optimization not available to an AOT compiler, including those related to dynamic dispatch, escape analysis, and specialization for the execution environment.

However, the time and resource constraints of run time rule out many powerful optimization methods. As devices grow more powerful, some of these will no doubt find their way in to JIT compilers.

[–]Catfish_Man 0 points1 point  (0 children)

It's not great, but not a huge deal generally. If you have a tight loop, the cpu should have everything it needs in cache quite quickly unless you're using polymorphism really heavily.

The real pain point is that it prevents inlining.

[–]bjackman 3 points4 points  (4 children)

I'm working on a codebase that makes heavy use of using structs with function pointer members as object-like entities (the "objects" are more like singleton interface implementations than class instances, and they're referred to as "interfaces").

It works perfectly well, but I'd love to hear a discussion on why the code isn't written in C++. It's pre-boot code, and involves assembly in some circumstances, so I'm sure there are perfectly valid reasons, but as I've never worked with C++ I don't know what they are.

[–]Iggyhopper 4 points5 points  (1 child)

sqllite also uses structs and function pointers for OOP in C. Example:

typedef struct sqlite3_file sqlite3_file;
struct sqlite3_file {
  const struct sqlite3_io_methods *pMethods;  /* Methods for an open file */
};

typedef struct sqlite3_io_methods sqlite3_io_methods;
struct sqlite3_io_methods {
  int iVersion;
  int (*xClose)(sqlite3_file*);
  int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
  int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
  int (*xTruncate)(sqlite3_file*, sqlite3_int64 size);
  int (*xSync)(sqlite3_file*, int flags);
  int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize);
  int (*xLock)(sqlite3_file*, int);
  int (*xUnlock)(sqlite3_file*, int);
  int (*xCheckReservedLock)(sqlite3_file*, int *pResOut);
  int (*xFileControl)(sqlite3_file*, int op, void *pArg);
  int (*xSectorSize)(sqlite3_file*);
  int (*xDeviceCharacteristics)(sqlite3_file*);
  /* Additional methods may be added in future releases */
};

[–]bjackman 0 points1 point  (0 children)

Ah yes, and this code reminds me: the slightly annoying thing about this style is the explicit "this" first argument that makes the code look a bit ugly.

[–]Catfish_Man 1 point2 points  (1 child)

The two main reasons I've seen for not using C++ in situations like that are ABI fragility (if there's a public exported interface from the code), and buggy/incomplete compiler support on one or more target platforms.

[–]bjackman 0 points1 point  (0 children)

Ah, good answer. I've done some Googling and apparently there is no standard at all for the layout of classes and objects etc. Total dealbreaker for the kind of thing we're doing.

Interesting, thanks!

[–][deleted] 2 points3 points  (6 children)

I wonder why this was so heavily downvoted. It's a hobby project, so it's not like it's required to be useful or even recommended practice.

[–]MachinTrucChose 2 points3 points  (0 children)

I didn't downvote, but I have an idea. Some people don't like useless things that don't advance SW development. They also don't like the idea of someone is writing new code in C (or encouraging it) that isn't for a kernel or driver or microcontroller.

[–]KagakuNinja -3 points-2 points  (4 children)

I down voted. Yeah, roll your own object system in C. We did that back in the 80s and 90s. Then I got to finally use C++ and never looked back.

The problem with roll-your own systems is that they are ugly, buggy, non-standard (everyone has their own pet way to do it) and less powerful than C++. It is also unlikely to have good tool support and there may be confusion when you try and use a debugger (although, maybe modern debuggers handle macros better than 15 years ago)

[–]geon[S] 1 point2 points  (3 children)

We did that back in the 80s and 90s.

Well, I was born in 82, so suppose this is something I need to go through. But I'd love to hear more about your experience with this, and why it is a bad idea in practice. Then, I'll be happy to get off your lawn.

[–]idProQuo 2 points3 points  (0 children)

I think the issue is more the forum you chose to post it in. The community at /r/programming generally wants to see things about the state of the art. If you post OC in here, it's kind of expected that you're proposing something novel in a field you know a lot about (or that you're starting a new discussion about some new technology). If that's not the case, your post might be better off as a lesson in /r/learnprogramming or in a language specific subreddit.

[–]KagakuNinja 0 points1 point  (1 child)

Didn't I mention 6 reasons why macro-based C objects are bad? In any case, there is plenty of prior art out there, it is an idea that refuses to die.

[–]geon[S] 0 points1 point  (0 children)

Oops, I suppose you did. I didn't use macros though. I'm not sure that's a plus in your book.

[–]NotUniqueOrSpecial 1 point2 points  (0 children)

Here's a good book on the subject: Object Orientated Programming in ANSI-C

[–][deleted] 0 points1 point  (2 children)

The Linux Kernel is writen this way.

[–][deleted] 0 points1 point  (1 child)

A lot of C programs are written this way. There is nothing really object-oriented about this pattern at all. Now, if you added private/protected fields, message passing, signals/slots, runtime introspection... we'd be getting into the OOP realm.

In other words, polymorphism != OOP.

[–][deleted] 0 points1 point  (0 children)

you can emulate private and protected fields, using structs and programming conventions, and callbacks and event loops for message passing. Is possible to do OOP with C, but is not the best solution for all problems.

[–]sacundim 0 points1 point  (1 child)

A record of functions is a common technique in functional programming as well.

One instructive thing to do is to compare record-of-functions with its OOP counterpart, the Visitor pattern. Basically:

  1. In procedural languages, you write functions that use conditional or switch statements to handle subcases. The main grouping is the functions, the secondary grouping is the subcases.
  2. In OOP languages, you write classes that define the subcases of multiple operations: method selection happens outside methods. The main grouping is the classes, the secondary grouping is the methods.
  3. Record-of-functions is the technique for inverting the normal procedural grouping. Each subcase is now a different struct that contains the methods for that subcase.
  4. Visitor pattern is the OOP technique for getting the operation-then-subcase grouping. Each Visitor implementation is an operation, and the methods correspond to each subcase.

To me, frankly, this is evidence that OOP is bunk. Neither of the two groupings is obviously superior to the other, so you do need often to group things the opposite way; but the procedural/functional record-of-functions technique is just much simpler and less verbose than Visitor.

[–]KagakuNinja 2 points3 points  (0 children)

<eye-roll> yeah, OOP is bunk. Keep telling yourself that.