10 ms·
Can you use a class in C?
- D4ckard 3y agoHey , first time posting something I've made here I think :D. I'm excited to hear what you think about it!
- lmz 3y agoI'd suggest removing the part where you use malloc to allocate and just skip to the part where you use new and link to libstdc++. At the malloc part I was wondering what the point of the whole thing was when you're basically using none of the C++ logic and rewriting everything in C.
- rezonant 3y agoAlso the Rational usage example in C++ is using stack allocation. It's weird to then pretend like heap allocation is equivalent in the C example. I think making stack allocation work in this case would probably be difficult, but it's worth pointing out in the article that the choice is intentional and not a misunderstanding of non-new allocation in c++. You could just use new() in the first example instead and avoid the whole issue.
- tragomaskhalos 3y agoYour 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.
- jwatzman 3y agoThe article discusses constructors and how to make them work under "Linking the C++ standard library". I wonder if placement new would run into the same linker problem that the article mentions -- I'll have to try it at some point :)
- krater23 3y agoAnd the stupidest thing, he uses this malloc in a .cc file, where it would no issue to just use new. Thats the point where I stopped reading. After auto main() -> int { ... } I didn't supposed that anything really meaningful can come...
- intelVISA 3y agoThanks, you spared me the horrors.
- flohofwoe 3y agoIME 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.
- rezonant 3y agoI agree, with the caveat that given a heavy duty, large, and well-understood and -designed C++ API, the tradeoff of using a tool-generated wrapper might be worth it, even if such a wrapper may be more obtuse than one specifically considered by a human developer.
- flohofwoe 3y ago> well-understood and -designed C++ API Unfortunately these seem to be quite rare (Dear ImGui is such a well-designed C++ API, and I actually also use the code-generated cimgui bindings more frequently now in my projects (haven't tried the 'new' official bindings yet). Interestingly the Dear ImGui C++ API is much closer to a typical "flat" C API than a typical class-based C++ API (Dear ImGui is mostly just a flat soup of functions wrapped in a namespace and with some mild overloading).
- rezonant 3y agoVery true. Designing the C API with intention can be a way to fix the mistakes sometimes :-)
- buserror 3y agoThe 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.
- lelanthran 3y ago> 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. I did too, all the way up to (IIRC) 2011. My happiness levels improved when I stopped working on C++ and, for the type of native-code problems that I used C++ for, used plain C instead. My goal is to ship code, with a trade-off between delivery dates, runtime performance and robustness. As the problem gets more complex, I find that C++ muddies the waters even more, impacting on all three axis' above.
- cesaref 3y agoI 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.
- tom_ 3y agoIndeed. It's super easy to create an opaque type in C: forward-declare a struct. So in the header: struct Rational2; struct Rational2 *make_rational(int,int); Then in the file: Rational2 *make_rational(int n,int d){ return (Rational2 *)new Rational(n,d); } void del_rational(Rational2 **pp){ delete (Rational2 *)*pp; *pp=nullptr; } And so on. You could probably arrange for it to be called Rational in both languages, starting out along these lines and then taking it from there: #ifdef __cplusplus class Rational { ... }; #else struct Rational; typedef struct Rational Rational; #endif And now you can could your C helpers from C++ as well, and the result is a genuine C++ Rational object that you can use either way. I don't think the ODR applies across languages, and I'm not 100% certain this would actually be an ODR violation anyway, at least not quite, but you'd need to ask somebody more qualified than me. (Another suggestion I would have is to bracket the entire header in the ifdef'd extern "C" {...} block, which limits the amount of extra crap you have in the header and per function. I think you can direct clang-format not to indent these blocks.)
- zabzonk 3y agoit's super easy to define an opaque type in c++ - you do it just like you do in c. you don't need to jump through these hoops.
- tom_ 3y agoQuite. And in fact, in this specific situation, in C++ you don't need to jump through any hoops at all, because you have the Rational class there already, ready for use. This whole business only exists to provide a C-friendly wrapper for this existing C++ class, along the lines of the one proposed in the article, with a couple of tweaks that I think would improve it.
- mtlmtlmtlmtl 3y agoBecause 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.
- jjgreen 3y agoall pointers are the same size in C Generally true, but not guaranteed by the standard: https://stackoverflow.com/questions/1241205/ https://stackoverflow.com/questions/1241205/
- rezonant 3y agoParticularly function pointers may have different sizes than what you'd expect. Thus it's best practice to always use sizeof() for the specific type of pointer you are interested in if you need to know it at runtime just as with any normal non-pointer type.
- isidor3 3y agoFrom that same post, though, referencing the C 11 standard > "All pointers to structure types shall have the same representation and alignment requirements as each other." And so the concern wouldn't apply to this pattern?
- deleted 3y ago[deleted]
- crabbone 3y agoTraditionally, pointers to functions were an exception. But I haven't red the new standards. I just assume pointers aren't all the same size, and don't try to rely on their size.
- mtlmtlmtlmtl 3y agoRight, my bad. It's really struct pointers that are guaranteed to be the same size.
- sharikous 3y agoMy 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.
- rezonant 3y agoAs with all of the languages you mention, consuming C from the other language is trivial. And since C++ is a superset of C that is certainly true there. Consuming the other language from C is difficult without a C compatible runtime API to help bridge the gap. Of course the issue for C++, unlike the other examples here, is that it does not have a runtime layer that can be used to bridge the gap. So we either write a wrapper in C++ using extern C, or use a tool to do it. It seems there was a GSOC effort for SWIG to generate C wrappers for C++ libs but it might not have made it all the way? I don't see C as a target language on SWIG's site. Still, a bespoke, high level design for the C wrapper is always going to be less painful for the consumer.
- zabzonk 3y ago> Of course the issue for C++, unlike the other examples here, is that it does not have a runtime layer that can be used to bridge the gap neither, per standards, does c
- rezonant 3y agoYes, but C is simple (and standard) enough that higher level runtimes have more or less universally managed to construct decent FFI mechanisms to access it. C++ FFI would be kind of feasible if mangling were standardized (for instance) :-/
- SubjectToChange 3y agoStandardizing name mangling would only be a baby step towards C++ ABI compatibility, sadly.
- grumblingdev 3y agoIt 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 https://www.youtube.com/watch?v=oKg1hTOQXoY
- danhau 3y agoThank you. You put into words what I was thinking. The best paradigm in programming has to be procedural style. The cleanest code I’ve written and read has always been procedural. I even maintain a monster VBA Word Macro at work and - after some much needed refactoring - its pretty easy to understand (ignoring VBA’s warts). The flow of logic and dependencies on data is much easier to follow in procedural.
- thewix 3y agoI prefer functional. Procedural is not restrained enough and allows for mutable global state. This can go off the rails when multiple people are in the code. Functional stresses composition so functions should stay small, and ADTs allow for all effects (errors!) to be represented. Strong typing helps reduce the number of tests I need to write, as well.
- 3y ago
- hwc 3y agowhy not forward decare a struct type in `ifndef __cplusplus` and use pointers to that type instead of `void*` pointers?
- krater23 3y agoHe uses malloc instead of new in a .cc file. I don't think that he realized that all what he is doing is overcomplicated at all.
- masfuerte 3y agoThis 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.
- FreshStart 3y agoYes, but the performance penalty becomes glaringly obvious as you are constantly dereferencing (composition, relationship and function) pointers and thus generate misses..
- WhereIsTheTruth 3y agoclass is a mistake, all C need is proper union (tagged union) Both Rust and Zig for example nailed it
- kazinator 3y ago> 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.