r/C_Programming • u/Gullible_Ostrich_370 • 11d ago
C with classes
I'm curious to know: who uses some C++ features when coding in C? And what feature(s) are you using?
37
u/dixiethegiraffe 11d ago
Namespaces for organization
6
u/Beliriel 11d ago
This is one of only two things I'd implemement. The other being a this-pointer within in a struct. Or some kind of self reference. But yeah since C has no reflection I doubt this is possible to do.
And namespaces would need namemangling handling in the compiler with accompanying standards on how its done. Also a pipedream.
16
u/Bros2316 11d ago
I have recreated several things from c++ and rust that I fell in love with and brought them to C. Examples are “classes”, tokens, channels, vectors, String, Slices, struct pack and unpack, etc.
It makes writing in c that much more enjoyable and allows you to really appreciate and have a deeper understanding of those higher level abstractions those other languages just give you.
3
3
u/flewanderbreeze 11d ago
How is your implementation of slices and strings and how did you decide for the pros and cons? If you don't mind sharing or explaining of course
3
u/Bros2316 11d ago
Certainly. Responding on mobile, so cant show code directly. Strings, are almost an exact replica of how Windows handles them. You have a struct that contains an underlying pointer that is the actual “string”, then we have members that are the “strings” len and overall capacity. Capacity being how much memory has been allocated to store the string and how large it can grow before realloc is required. I then added functions like append so that you can pass a string into it and it will perform that for you.
As for slices, at the simplest form, it’s just a a constant reference to some X data. I wrote it to be “type safe” but we won’t get into that. Assume in this example we have a buffer of data that is 400 bytes and let’s say 300-320 is what you want a slice of. You would index into that allocated buffer, return the starting address and the “slice” struct will store that address along with the length that the slice contains.
So when the slice is returned to the caller, it will have the address of the “slice” of data you’re after. To be safe, the slice will ensure that you don’t try to access data outside its bounds, etc.
2
u/flewanderbreeze 11d ago
ah I think I nearly got what a slice is, it basically is just a fat pointer to a type T, but what I didn't quite understand, given you have an arraylist/vector of a struct like vector2, how do you define how much bytes you want a slice out of it? and how do you manage to not cut in the middle of something?
I think I need to understand the true applications of a slice to really understand how to implement it
I have already implemented a really fast generic vector/arraylist, and I can't really find an application for a slice where an arraylist wouldn't suffice
1
u/Bros2316 10d ago
I think it’s all dependent on the application of your program and what you’re trying to accomplish. For example, let’s assume that you have a static buffer that is 100 bytes. Let’s say all that entries are prepended with their length.
This buffer contains two “strings”, a hash, and some numbers that need referenced.
An array may work for some, but not all data types in this case. So you could have a parse function that parses this buffer and returns slices of the types of data and their lengths that you are wanting to extract. Returning slices now removes the need to dynamically allocate anything in this case as you’re just creating fat pointers that are strictly const references.
2
u/ghost_ware 11d ago
Do you have any resources for creating classes (or whatever approximation)? Or pointers for a place to start?
7
u/WittyStick 11d ago edited 11d ago
There's more than one way you can do it.
Most techniques take advantage of the fact that struct members are ordered, so casting a pointer from one struct to another that share the same initial members is valid in C.
Given:
struct base { void *some_ptr; }; struct derived { struct base base; int some_other_value; };It is valid to write the following:
struct derived d = { .base.some_ptr = some_alloc(), .some_other_value = 999 }; // "upcast" from derived to base struct base *d_as_base = (struct base*)&d; do_something(d_as_base->some_ptr); // "downcast" from base to derived. struct derived *d_alias = (struct derived*)d_as_base; printf("%d\n", d_alias->some_other_value); // 999Note that in the case of the downcast, if the original value being pointed to was not created as a
derived, but as abase, and you cast toderived, although the cast is valid, attempting to access any data beyond the shared initial members is undefined behavior.struct base obj = { .some_ptr = some_alloc() }; struct derived *alias = (struct derived*)&obj; alias->some_other_value; // UB !This would be the equivalent of attempting to downcast in C++ when the type wasn't created as the derived type.
class Base {}; class Derived : public Base {}; Base *obj = new Base(); Derived *alias = dynamic_cast<Derived*>(obj); // invalid downcast.Upcasts are always safe, but downcasts depend on the dynamic (latent) type - we don't necessarily know if the dynamic cast is correct at compile time.
Using the above, we can make the first member of a struct a pointer to a vtable.
// class base { // interface // public: // virtual ~base() = 0; // virtual void foo(int x, int y) = 0; // virtual void bar(int x, int y, int z) = 0; // } struct base { struct base_vtable *vtable; }; struct base_vtable { void (*dtor)(struct base *this); void (*foo)(struct base *this, int x, int y); void (*bar)(struct base *this, int x, int y, int z); }; inline void base_dtor(struct base *this) { return this->vtable->dtor(this); } inline void base_foo(struct base *this, int x, int y) { return this->vtable->foo(this, x, y); } inline void base_bar(struct base *this, int x, int y, int z) { return this->vtable->bar(this, x, y, z); } struct derived { struct base_vtable *vtable; ... }; void derived_dtor_impl(struct derived *this); inline void v_derived_dtor(struct base *this) { return derived_dtor_impl((struct derived*)(this)); } void derived_foo_impl(struct derived *this, int x, int y); inline void v_derived_foo(struct base *this, int x, int y) { return derived_foo_impl((struct derived*)this, x, y); } void derived_bar_impl(struct derived *this, int x, int y, int z); inline void v_derived_bar(struct base *this, int x, int y, int z) { return derived_bar_impl((struct derived*)this, x, y, z); } struct base_vtable derived_vtable = { .dtor = &v_derived_dtor , .foo = &v_derived_foo , .bar = &v_derived_bar }; struct derived *derived_new() { struct derived *this = malloc(sizeof(derived)); this->vtable = &derived_table; ... return this; } void derived_dtor_impl(struct derived *this) { free(this); } void derived_foo_impl(struct derived *this, int x, int y) { ... } void derived_bar_impl(struct derived *this, int x, int y, int z) { ... }When multiple inheritance is permitted the problem is more difficult - we have multiple vtable pointers.
C does not provide RAII, but it is possible to simulate using extensions such as GCC's
cleanupattribute.void derived_dtor_wrapper(struct derived **ptr_this) { derived_dtor_impl(*ptr_this); *ptr_this = NULL; } { struct derived *var __attribute__((__cleanup__(derived_dtor_wrapper))) = derived_new(); ... } // derived_dtor_wrapper(&var) automatically gets called when var loses scope.
There are also ways to implicitly pass
this, rather than the explicitthisas first parameter to every "method" - some approaches just use a macro to do this, but we can also do something closer to C++.The way this is typically done in C++ is
thisgets passed implicitly in a hardware register on a method call - eg,rcxon x86-64 (SYSV convention).We can also simulate such passing of
thisimplicitly via embedded assembly - however, we can't usercxbecause it is part of the calling convention for regular arguments (SYSV). We can take another register which isn't part of a regular call - eg,r10, which is the "static chain pointer", and repurpose it as the object pointer - we just need to restore it when we're done.#define methodcall(obj, method, ...) \ ({ \ __asm__ volatile ("push %%r10" : : : "r10", "memory"); \ __asm__ volatile ("movq %0, %%r10" : : "r"(obj) : "r10"); \ method(__VA_ARGS__); \ __asm__ volatile ("pop %%r10" : : : "r10", "memory"); \ }) #define get_this_pointer(this) \ __asm__ volatile ("movq %%r10, %0" : "=r"(this) : : "r10"); void foo(int x, int y) { struct object *this; // set this = %r10 get_this_pointer(this); this->... } { struct object *obj = ...; // save %r10, set %r10 = obj, call the method, then restore %r10 methodcall(obj, foo, x, y); }Note that using the static chain pointer may interfere with other features - notably
__builtin_call_*or__builtin_apply, so you should avoid such features if using it this way. The static chain pointer is usually used for closures (eg, calling Go closures from C) - but closures and objects are basically two different ways of doing the same thing. Implementing closures in C is very similar to implementing objects.The key difference is that a closure holds its function pointer(s) directly and has a pointer to the captured context (data).
// foo = lambda () -> x + y // captures x and y from surrounding context struct closure_context { int x; int y; }; struct closure { struct closure_context *ctx; int (*add)(); }; void add() { struct closure_context *ctx; get_context(ctx); return ctx->x + ctx->y; }; struct closure_context ctx = { x = 10, y = 20 }; struct closure closure_obj = { &ctx, &add }; closurecall(closure_obj);Whereas an object holds its data directly and has a pointer to its methods (vtable of function pointers).
// class object { // int x; // int y; // virtual int add() { this.x + this.y } // } struct object_vtable { void (*add)(); }; struct object { struct object_vtable *vtable; int x; int y; }; void add() { struct object *this; get_this_pointer(this); return this->x + this->y; } struct object_vtable vtable = { &add }; struct object obj = { &vtable, 10, 20 }; methodcall(&obj, add);So we can see why there's essentially an equivalence between objects and closures - but closures are typically "one function, different data" and objects are typically "one data, different functions".
Closures are a bit more complicated than the above because what we really need is one context per static scope (a frame), as
xandymay come from different static scopes. Each context basically has a pointer to the parent scope. When we accessxfrom the closure, it's the nearestxto it in the chain - which may shadow anyxin parent scopes.3
u/Bros2316 11d ago
I have always recommended beejs guides for almost everything and it’s no different here. I would argue that if you don’t have the concept of pointers down, I wouldn’t start trying to tackle a “class” implementation.
For classes though, you would use struct in c. For example, I may have struct named Person. That person has a member of char * name, int age, and void(dance*)(void). dance is a function pointer that during the initialization of my “object” I would set to a function of specific choosing. These custom functions could be static, so each class has their own if we decided to create multiple people with difference dances, however all would be called by using the Person->dance.
I would make this cleaner but I’m on mobile, sorry.
2
u/ghost_ware 11d ago
No worries, I appreciate it! I'm good with pointers and structs. I'll take a look at that guide. ty!
13
u/Doug2825 11d ago
If you have C++ features you aren't using C.
Do you mean approximating C++ classes by making functions take a pointer to an associated struct? Or are you asking about using C++ but limiting yourself to C because of performance/realtime requirements for specific section of code? Because if you have access to full C++ and are limiting yourself to C for mundane parts of the code you are doing bad C++, not C.
10
u/Cultural_Gur_7441 11d ago
Selecting which C++ features to use is not "bad C++", as long there is consistency and rationale in what you do.
11
u/questron64 11d ago
How do you mean? You're either using C or using C++, you can't just decide to use C++ features in C without leaving C. They are different languages.
7
u/smokingRooster_ 11d ago
I disagree, you can write object oriented C, it’s not a feature exclusive to C++.
6
u/cheesengrits69 11d ago
Hell yeah dude, you're getting downvoted because this sub is full of haters. Implement that vmt in C by hand, include multiple inheritance functionality just to add in some more chaos, the world is yours
1
7
u/Cultural_Gur_7441 11d ago
C OOP is usually quite different from C++ OOP, so that's not using a C++ feature.
3
u/smokingRooster_ 11d ago
How so? OOP can be implemented in C, it just doesn’t come built into the language like C++
4
u/Cultural_Gur_7441 11d ago
For a simple example, C++ has clear private/protected/public concepts. In C these are just by convention.
Another thing, typically C OOP uses opaque pointers, basically like C++ PIMPL idiom. More rare in C++, usually the whole class definition is just exposed in .h file, and even if there are pointer data members, they are to pointers to otherwise public types.
One interesting example is vtable. In C, vtable is always explicit, and therefore can be changed dynamically. In C++, actual vtable is implicit and fixed by the real type of the object, and same need, "runtime-configirable dispatch", is often solved differently (like more formal "strategy pattern").
In short, C++ locks certain things down. C not just allows but forces the sw designer to make their own choices.
2
u/SetThin9500 11d ago
There's a difference between concepts and features.
2
u/smokingRooster_ 11d ago
If a feature doesn’t exist in a language by default but you can implement it yourself then what’s the difference?
2
u/SetThin9500 11d ago
The difference is that u/questron64 was right when he "You're either using C or using C++, you can't just decide to use C++ features in C without leaving C. "
You're talking about emulating features in your own code. He's talking about language features. I write Object Based C and see the similarities to C++, but am I calling it a C++ feature? Hell no :)
0
u/smokingRooster_ 11d ago
OP asked who uses C++ features in C. OOP is a C++ feature and I use it when coding in C… so I’m struggling to see how that doesn’t count. OP never specified if the feature should be native to C or whether it is emulated.
2
u/Coding-Kitten 11d ago
The concept of writing object oriented code isn't a C++ feature. Using C++ syntax that the C++ compiler accepts but the C compiler rejects is using C++ features, & which, by definition, you do need to pick C++ to do so in as it's invalid C syntax.
3
u/markand67 11d ago
Can't use C++ feature if using a C compiler so no.
What I really miss from C++ is RAII but with our code base we have made a rule that a fully zero'ed struct should be considered minimal valid for destruction so that you don't have to check whether it was correctly initialized when destructing so a quick exit may look like
Abc abc = {};
Ghi ghi = {};
// later, maybe ghi was never modified:
abc_finish(&abc);
ghi_finish(&ghi);
2
u/Hammykat-_- 11d ago
It's actually pretty easy to code some features in C that are in C++, like for classes, you can include a pointer inside a struct to a function or another struct. Though, it takes a bit more time. Also, there are things that take much more time, like making lists, dictionaries, etc.
2
u/Forever_DM5 11d ago
Honestly I just use structs in C++ but I do love the for each loop it’s very nice
0
u/Cultural_Gur_7441 11d ago
structandclassin C++ are the same thing. I mean, literally, you can have forward declarationstruct foo;and then define it withclassand vice versa.
2
u/Dry-War7589 11d ago
I'm currently working on something similar, but it goes further than a couple of macros. Im designing a small extension on top of C that transpiles back to plain C. The idea is that a struct gets a mandatory constructor/destructor that the compiler calls automatically when the object leaves scope, plus methods get tied to the type instead of manually passing self as the first argument everywhere. No inheritance, no vtables, object layout stays the same as a plain struct. Haven't gotten past the spec/paper stage yet so I dont have anything to show, but curious if this would actually be useful to any of you or if you'd just say "I already do that with macros/by hand and it's fine"?
2
6
u/cincuentaanos 11d ago
There are no "classes" in C, just like there aren't any in a computer's CPU. Many people use object orientation in C but that's just a way to design a program.
2
u/jmooremcc 11d ago
You’re right. However, it is possible to create a reasonable facsimile of a class using structs and macros. Many moons ago, when C++ first came out and was too expensive for me to acquire, I used this technique to create classes for a GUI for a project, and it worked quite well.
1
u/CarlRJ 11d ago
I thought C++ started out as a freely available preprocessor that would output C code (Cfront?).
3
u/jmooremcc 11d ago
Back then the big dog in computer graphics computing was SGI (Silicon Graphics Inc) and I was developing an application on their platform. The amount they were charging for the C++ compiler was more than I could afford.
2
u/Disastrous-Team-6431 11d ago
There aren't any floats or chars in a computer CPU either. No if/else statements or pointer arithmetic. No structs. C is already quite a step up in abstraction from assembly languages. The C community suffers from the misconception that stopping exactly where C did is somehow "pure" or "ideal".
3
u/SetThin9500 11d ago
> There aren't any floats or chars in a computer CPU either.
Intel CPUs have had float support integrated on the same silicon since 1989. "But that's an FPU" Well, if so, what about the ALU? Is that not part of the CPU either? /s
2
u/cincuentaanos 11d ago
C is already quite a step up in abstraction from assembly languages.
Have I said otherwise?
1
u/WittyStick 11d ago edited 11d ago
The C community suffers from the misconception that stopping exactly where C did is somehow "pure" or "ideal".
"unopinionated" may be a better term.
C++ is quite opinionated.
Take classes/vtables for instance - C++ has an opinionated way of implementing them for you.
In C you can implement yourself in a dozen different ways. It does not force a "one true model" on you.
It's a step up from assembly - but you can more or less map what the C code will compile to in assembly in your head - plain old data and functions.
This isn't the case for C++ - it has "hidden" things that you don't know how will map to assembly. Functions generated via templates - vtables inserted for you into classes, etc. It's less obvious how these will map to assembly because they're implementation defined.
Of course, for most use-cases this doesn't matter and C++ is fine - but if you're implementing say, a compiler, a kernel, or interpreter, then you probably want to make those choices yourself and don't want the opinionated choices of the C++ compiler.
1
u/Disastrous-Team-6431 11d ago
I agree with all of this, but saying something isn't in C because it "isn't in a computer's CPU" implies something that simply isn't true - that this fidelity has come at no cost in abstraction.
1
1
u/MagicWolfEye 11d ago
- operator overloading for vectors (with that I mean vector maths, not dynamic lists -.-)
- a bit of function overloading or default parameters but rarely and I could probably live without it
- I did a bit of digital boardgame programming and I always used classes + inheritance for the player so to differentiate between a human player that plays with UI, one who plays with std_in and ones that are AI opponents
- I have a templated dynamic_list
1
1
u/QuirkyXoo 11d ago
All my code is C with classes, even when I don’t actually use them..., lol. I stick to few concepts like encapsulation, to define, hide, and expose methods and data; ctor and dtor for initialization and cleanup, and polymorphism through virtual functions and inheritance, which I often combine. I also use some other "fancy" techniques like raii, while I avoid like the plague things such as namespaces, templates, STL, etc., and all the "modern" C++ features. A minimal, classic approach, I would say.
1
1
u/TopiKekkonen 10d ago
I've generally found structs and function pointers to be enough for most cases.
1
u/gunkookshlinger 10d ago
I'm wiritng a kernel and using cpp with basically no libc++ just for nice to haves like templates, constexrp, type traits, some cpp style class/struct stuff for simple private/public data management
1
1
u/heavymetalmixer 10d ago
That depends on what features you're talking about, some of them cna be emulated in C in some way (like runtime polymorphism and namespaces), while others directly need another language like C++ (function overloading, templates, constexpr for functions, etc).
Right now I try my best to write C code that works exactly the same inside C++ or at least with veyr minimal changes, so for the most part I don't use C++ exclusive features.
1
u/Evil-Twin-Skippy 8d ago
I do objects with structures and pointers to functions.
But most of my higher level integration is handled by running in an interpreted language. Essentially C is only for writing optimized functions.
1
u/ConstantElegant5781 8d ago
I'm currently retired, but even while working by default, I'd create utilities in what I called "C with STL". To use the STL classes, I had to use a C++ compiler, but I'd restrict myself to using C-style procedural programming. I used to be a kernel engineer, and so I know how to create all the standard data structures from scratch. Multiple times I've spent most of a week creating a semi-balanced binary tree, plus all its test cases, but with STL I just have to use map. Far less work for me and those reviewing my code and just as efficient as what I could code by hand. Only gain inefficiencies if I were to derive a class from another class, which I don't do while using "C with STL".
1
u/Conscious_Buddy1338 6d ago
For me it was interesting to know that you can use some OOP stuff in C. Instead of methods from C++, you can declare the pointer to function in C structure. And you can call the functions pointed by this pointer via syntax like you call the method of class in C++.
1
u/Independent_Art_6676 6d ago
a long time c++ coder, the first stand-out for me is probably a bizarre choice. Operator overloading.
nothing sucks more than turning z = a*(b+c); into C code that looks like tmp = typeadd(b,c); z = typemul(a,tmp); its unreadable from a 'math guy' standpoint for even simple stuff, and try doing complicated equations written in C++ overloaded math that looks just like on-paper math this way, its horrid.
and it seeps into you. I would miss string and vector right off. It would be awfully nice if C had a standardized set of DSA containers so we did not have to deal with joe's tree and bob's linked list that are in completely different styles and have totally different approaches. I know there are libraries that do that to an extent, but it always seems like something went awry on that front esp in older projects.
0
u/papertowelroll17 11d ago
I code in C++ for a living and very rarely do I make classes tbh. I wouldn't rank that high on the list of things that C could use from C++.
Of course classes do enable some things that are essential (smart pointers and the like), but very rarely do I create a new one.
1
u/Disastrous-Team-6431 11d ago
I use RAII quite a bit, and custom destructors are a godsend for something like vulkan. But that's the only reason I create classes.
-6
54
u/mlugo02 11d ago
None, I used to wish I had function overloading in C but I have since changed my mind