Can you use a class in C?

  • It took me a long time to realize that I actually prefer not using classes.

    It's the small things in programming that can make a huge difference. It always felt like I should be using the C++ way because it was slightly dry-er and looked nicer, and I could have all these OO features.

    Like when you see `int get_numer(void *r)` or Python's `def get_numer(self)`, or Go's `func (rational *Rational) GetNumer() (n int)`, you think: "how silly, why does the object pointer need to be in scope, just use `this`". But this tiny thing is liberating. It allows you to see that everything is just functions operating on data. And that a "method", is just a function that is taking _the entire object_ as a parameter...and hence a dependency. Which allows you to think: hmm, does this function really need to depend on the entire object...maybe it can be a separate utility function all by itself without any connection to the class. And maybe it doesn't actually need access to any of the other functions in the class...and maybe the class could be split up...etc.

    I just watched a [talk](1) by Alan Kay the inventor of SmallTalk and the phrase "object-oriented" who never stops shitting on C++.

    Yet OO is still absolutely everywhere.

    [1]: https://www.youtube.com/watch?v=oKg1hTOQXoY

  • IME the best way to wrap C++ libraries for use in a C code base is to move at least one step higher then just wrapping every C++ class method with a C function (because this will result in an absolutely miserable C experience - you're basically writing C++ code in C, but without all the C++ syntax sugar).

    Instead write a higher level module in C++ on top of the C++ library which implements some of the "application logic" and only exposes a very small app-specific (and non-OOP) C API to the rest of the application.

    For instance with Dear ImGui I often write the entire UI code in C++ and then only expose a minimal C API which connects the UI code to the rest of the application (which is written in C).

    Same with managing C++ object lifetimes, let the C++ side take care of this as much as possible, and don't expose pointers to C++ objects to the C side at all.

  • Because all pointers are the same size in C, and a pointer to a struct always points to the first member of the struct, you can do inheritance by having the "superstruct" be the first member of the substruct:

      struct foo {
        ...
      };
    
      struct bar {
        struct foo super;
        ...
      };
    
    You can now safely pass a pointer to bar into a function that takes pointer to foo.

  • My philosophy on this is to treat C++ and C as completely different languages (as they actually are), like say Java and C or python and C. Yes it's nice that some part of the header file can be parsed in both languages and that most of the syntax is similar.

    But once your expectation is that you have to do all the work at the FFI boundary it's less frustrating than to experience all the small mismatches as compiler errors or annoying runtime errors.

  • The only times I had to do that I.... converted the whole heap of C++ to C and used that instead. You rarely see libraries using all the most complex constructs of C++, and quite frankly, if they do, I'd rather stay clear of that pile of bloatware :-)

    I wrote and shipped C++ as a job for many years -millions of lines I'm sure- and I've 'reverted' to plain C around 2007 or so, and I couldn't be happier.

  • Hey , first time posting something I've made here I think :D. I'm excited to hear what you think about it!

  • I think you can do better than this void* type this equivalent - if you consider what is happening with FILE* when you use stdio.h, you have basically a class interface, and i'd follow this pattern.

    There is no reason to use void*, create a distinct type which can be opaque if you want, and then you can hide the implementation details in the C++ implementation to call through to the C++ classes. You get some degree of type safety this way.

  • Your allocation method - malloc + cast - may work for simple classes but overall it's a serious no-no; think you should either be using new/delete, or, if sticking with malloc /free, use placement new and an explicit destructor call respectively.

  • This is kind of what Microsoft's COM gives you. You have to write your classes in a particular way but then you get a well-defined API that can be consumed in many languages, including C.

  • > We successfully created a class in C++ that we can now use in C!

    It's substantially easier to bind FFI to it if it's usable from C.

    If it's usable from C, it's usable from umpteen other languages.

    Thou shalt not write a C++ component without a C API.

  • Yes, but the performance penalty becomes glaringly obvious as you are constantly dereferencing (composition, relationship and function) pointers and thus generate misses..

  • why not forward decare a struct type in `ifndef __cplusplus` and use pointers to that type instead of `void*` pointers?

  • class is a mistake, all C need is proper union (tagged union)

    Both Rust and Zig for example nailed it