Yes, Structures in C can be used as classes. And yes, this includes polymorphism.
My solution was to put a callback in the structure that would point to the implementation that was polymorphic. This allowed me to pass the struct around and then call the specific implementation at the right time.
When you write OOP in C you do not need to make it look like OOP language. You just need to understand what OOP is about and use the existing language to do what you need it to do as efficiently as possible.
As to spending time on writing functionality that is readily available in other languages... sometimes you just don't have a choice. There are programming environments where ANSI C is the only language available as was in my case.
Please, try not to be too dismissive to the things you do not understand.
I think you mean function pointer. But yeah, that's the usual way to do it, although a bit wasteful in terms of memory compared to a vtable (but needs one less indirection to jump to the method).
For me, a callback, is when you have two modules and the function pointer is used to call back the functionality from the calling module in an environment and at a time determined by the called module.
A polymorphic method is a callback used in a certain way in context of OOP. You have a callback from one module (the implementation) passed to another module that can call potentially many different implementations through the same interface (which makes it polymorphic). This callback is tied to the structure by the calling module, which makes it a method. You may have language support and have entire mechanism hidden from you, but this is essentially what is happening. A pointer to function that is passed to another module for execution.
Now, when you do "manual" OOP in C without building your own vtable macros and such, I think "polymorphic method" is a bit too much and "callback" is more fitting.
I never said it's impossible to do method polymorphism in C, I said nowhere in that article method polymorphism is implemented.
You obviously did not read what I wrote at first place, only saw what you wanted to see to make some kind of (bad) rebuttal without a single concrete implementation as an example. Of course it's possible to implement method polymorphism in C, which is what I said, the article linked isn't doing that anywhere, quite the opposite.
So no, C structs are not equivalent to Java classes, at all.
Well, not quite. You said that it isn't OOP without method polymorphism. So no need to fly off the handle and accuse others of misreading something you didn't write.
I said
> This isn't OO until you implement some form of method polymorphism.
which means the article didn't implement method polymorphism.
> So no need to fly off the handle and accuse others of misreading something you didn't write.
So you need to pay attention at first place instead of "flying off the handle" yourself and then accusing others to do so in a useless comment.
OO was just a failed paradigm anyway, I’m amazed anyone would try and shoe-horn it into C in the modern day.
The key to OOP is extracting the idea of types/concepts into interfaces that may have different implementations.
You don't even need a language that supports polymorphism as a first-class concept in order to do this, though it is certainly much safer. Python chooses duck typing, modern C++ uses "concepts," etc. You can do SOLID under any of this. Some languages or frameworks take it too far and think of everything as an instance of a base object, for no great reason.
I guess a summary of my thesis is that the "L" in SOLID is what makes an architecture OOP.
int CreateTheStatefulThing(blah* thing); DoStuffWithThing(blah* thing); void CleanUpThing(blah* thing);
To suggest OOP is the foundation of reusable code is simply wrong.
I said that "OOP has become the foundation," not "OOP is the foundation." There is a very big, historical, fact-based distinction between those two statements.
Modularity and reuse are completely different things. A highly modular codebase is more amenable to write reusable code, but many modular systems have been written with very little concept and code reuse.
Of course there was a measure of reusable code before OOP was a more formalized language for discussing it. But to say "you can do these things without OOP" is like saying you don't need structured programming to write maintainable systems. And it's like "ok, but you're still using "if," possibly "return," and you know... functions." Essentially the exact same ideas that the paradigm gave formal language to.
I think people who say that OOP wasn't responsible for the understanding of modern paradigms haven't read much of the theory behind it and have hyper focused on the poor series of books and articles that came out from the 1980s through about 2010 that encouraged hidden state ownership and inheritance of state.
You cannot separate theory and practice in the way you are attempting to. It’s not just “bad books” that lead to the abuse of inheritance that is so familiar, there were (and are) broken concepts that only became visible with time.
I highlighted “module” for a reason, because one of the key mistakes of practitioners was to view the element of re-use as the “class”. In reality if we want to create a reusable component the boundary is much higher. We create an api and a library or some other component that can be distributed.
Building a reusable component is much more work, and recognising when that is necessary and designing accordingly is an important engineering discipline.
To finish with a snipe of my own; I think people who take an OOP is primary view of the world need to do a little more programming in lisp to properly understand the fundamentals.
Look, you don't like OOP, who cares? I certainly don't. But that's different than trying to explain that you have a fundamental misunderstanding.
Whether you accept it or not, it's in your head now ;)
What’s my fundamental misunderstanding? What’s the straw man? Class == re-use?
I don’t hate OO by the way, it’s just not the first, only, or in my opinion best way to think about or structure code. I do think the expectations that OO would bring increased productivity, code-reuse, etc were not realised and hence why I referred to it as a “failed paradigm”. Not because it is bad from a theory perspective, but because in practice none/few of the things it was expected to deliver actually materialised. And also that the real challenges with “code reuse” are different. I have read the bad books, but also the good books.
I’ve done more OOP programming than anything else over my career, and it can be done well. I’ve also worked with and coded in extremely well written C code bases. I just can’t see the value that OOP provided over modular C. Perhaps you can enlighten me?
In terms of what this adds to C, I can only suggest that you study a large C project. I'd recommend the Linux kernel. You won't find first-class support for inheritance and polymorphism, but it's there. It is amazing how modifiable and extensible entire subsystems are, and when you examine it, you find the SOLID principles at work, which came from OOP study and practice.
I don't really understand what you mean by the way OOP is "structured." If you're referring to deep object hierarchies like Java or Qt, I don't really disagree. They attempt to force an architectural unity through object hierarchies. But then on the other hand, if you're saying that it isn't helpful to contemplate an architecture through the lense of types, the types of transformations and operations that can be performed on them, and packaging these operators and transformers into a group that can be passed to code that calls them abstractly, then I don't agree.
Amusingly, I don't think that second thing is what you're saying. But with your arguing against OOP, I don't really know how to interpret this. This is what OOP gave us language to describe. Of course some of these techniques existed before in LISP or C, but code bases of the day were largely NOT leveraging them in any large scale way. OOP gave us vocabulary for these concepts, as well as mainstreamed their use, similar to how functional programming mainstreamed the idea that a nonlinear or cyclic graph of executable functions must not allow shared state, even if hidden within objects, if you want to be able to maintain that code long term, and extend it. So it seems like you're arguing that OOP is bad, while neglecting the history that OOP brought us the common understanding of how modules should interact with each other.
I have previously worked on very large C code bases (10m+ loc) that were well written. I haven’t worked with the Linux kernel, but the “SOLID Principles” and “modifiable and extensible subsystems” certainly were evident in these code bases. You claim that this form of programming came as a result of the study and practice of OOP, but I think this cannot be right. The ideas pre-date this. These code bases, pre-date this. Much of the literature from the 70s covered these topics.
I think what happened through the later 90s/early 2000s was that OOP was universally embraced, but without theoretical backing. C programmers picked up C++ (I was one), without really understanding, and the result was a mess. From there I think good practices were rediscovered, not invented. I don’t credit OOP for the good practices, although I don’t disagree that some truly new things have been learned.
However, I still have to deal with the poor practices within OOP. Developers inherit all the time for convenience. Rarely do they think in terms of interfaces/contracts at the modular level (why is this class public? *shrug* why isn’t this class sealed? *shrug*) It’s this sort of thing that makes me annoyed at OOP. To this day, developers still sometimes think of every class they write as being a “reusable” component, and this I lay at the feet of OOP.
Classes and Objects are sometimes useful. That’s what I think of as OOP, and that’s the bit that I don’t see as very valuable.
"OOP has failed" should be read in context of the promise that everything can be effectively implemented with OOP, that you can make objects for everything and your entire program built out of it. The masses adopted it with pretty poor results -- and the poor results prompted people to say that "it has failed".
It just means that it has failed to live up to its promise. It does not mean OOP is not useful. Quite opposite, we have learned a lot about when it is very useful to use OOP. And also when it probably is a poor idea.
I personally mix and match OOP and functional styles. I will typically create objects to describe domain model because it is super useful to talk about operations on entities. But I will call these objects from functional context where it makes much more sense to create infrastructure from functions that can easily deal with different types of data.
No programming paradigm is clearly better than others and no programming paradigm is without flaws.
I would strongly suggest treating every person that says otherwise with extreme caution -- people tend to say these things at certain stages of their evolution as programmers until they have enough time to learn ins and outs of all things.
That’s not to say that nothing has been salvaged from the wreckage; languages like Rust are a great example of retaining what actually was useful.
Because it is not borne out by facts. The explosion of software since the 80's(?) was a direct result of OOD/OOP becoming the dominant paradigm.
Your posts remind me of this: https://twitter.com/awbjs/status/625082965257654272
Please do go on and prove how the explosion of software since the 80s was the direct result of OOD/OOP. Please be sure to reference Linus, Joe Armstrong, Alexander Stepanov. Surely such undeniable facts will be supported by relevant leaders in the field?
This quote from Eric Lippert clearly sums your position well “ “It may be that this success is a consequence of a massive industry that supports and is supported by OOP.” which is true by virtue of it being tautological.
We can do better than a tautology to answer the question posed in the title of the piece by observing that OOP style directly and deliberately maps well to the typical hierarchical organization of large corporations. Why do we hate working in OOP? For the same reasons we hate working for inflexible, siloed, process-heavy, policy-heavy, bureaucratic, mutually-distrusting teams in corporations. Why is it a success? For the same reasons that those corporations are successful: organizations with those characteristics have historically been strongly correlated with creation of value that increases the wealth of the billionaire class, and therefore billionaires pay for the creation of those systems.
It should be no surprise that OOP style is popular in corporations; OOP is the reification in code of corporate thought patterns.”
In a sense, Yes; because you are the one who made blanket statements like "OO was just a failed paradigm anyway," which is objectively wrong.
To elaborate on my comments, almost every language since the 80's has had very good support for OOP. Almost all reusable libraries/frameworks (especially GUIs) also are implemented using the OOP paradigm so much so that nowadays the kids don't even know of a world before "Objects". The success of OOD/OOP in the industry is self-evident.
If certain aspects of OOD/OOP have to be used carefully (eg. Implementation Inheritance) and how abuse of OOP (eg. everything is an Object) can make a mess of things, then that can be debated, but to dismiss the whole of OOD/OOP is just plain silly.
Coming to your "appeal to authority", let me clarify the nuances in their writings/quotes.
Linus Torvalds: His objection is not to OOP per-se (indeed the Linux Kernel uses a lot of OO patterns itself; see the articles by Neil Brown on lwn.net; links in my past comments) but certain aspects of it and in particular; to the C++ implementation of those. These are valid for his Kernel but not for others.
Joe Armstrong: His focus was on "Concurrency Oriented Programming with no Shared State". From that pov his design of Erlang "Processes" is exactly as Alan Kay envisioned OOP. Here is Armstrong himself: https://elixirforum.com/t/the-oop-concept-according-to-erlan... More here: https://stackoverflow.com/questions/3431509/is-erlang-object...
Alexander Stepanov: His background is in Mathematics and hence he looks at Programming from the pov of Abstract Algebra. His issue is with straitjacketing "Interface Inheritance" within a single type hierarchy whereas logically a subtype can span multiple hierarchies. This is also the reason Bertrand Meyer called "Multiple Inheritance" indispensable.
Eric Lippert: Your listed quote doesn't make sense, i am almost sure he was saying it "tongue-in-cheek" to draw attention to the fact that dysfunctional OO codebases are similar to dysfunctional organizations. It is not a value judgement on OOD/OOP.
In summary; OOD/OOP is a way of structuring Procedural/Imperative programming where global state is partitioned into smaller pieces along with their transformers which helps you manage Modularization, Types, Type hierarchies, Modeling real world "Objects", Reusability, Extensibility and Maintenance.
You don't need macros. You either call via the vtable, or you write a wrapper per interface per message.
E.g.
struct class {
void (*vtable[NUM_SLOTS]);
};
struct object {
struct class * class;
};
typedef struct object object;
const int STRING_LENGTH_SLOT = 0;
int string_length(void * self) {
return ((int (*)(void *))((object *)self)->class->vtable[STRING_LENGTH_SLOT])(self);
}
(EDIT: I forgot just how much gross casting is needed, and added examples of class and object)Such wrappers are fairly trivial to generate if you don't want to handwrite them.
If you want complex inheritance patterns, you might benefit from one more level of indirection, see e.g. Protocol Extension: A Technique for Structuring Large Extensible Software-Systems (M. Franz, 1995). That paper is for Oberon, but it's easily translatable to C - did that way back.
Upside is it let's you group functionality in interfaces with less of an explosion in vtable size, downside is the cost of one extra pointer indirection.
I know, thus the /s for sarcasms.
> Such wrappers are fairly trivial to generate if you don't want to handwrite them.
It's fairly irrelevant to my point, which is that C structs aren't equivalent to Java classes in anyway.
Of course you can implement OO is C, I've done enough GObject in my life to know how horrible it is to use in practice.
From a user point of view you end up doing things like:
string * str = string_new();
int len = string_length(str);
So it looks just like most other typical C code using prefixes per interface and passing pointers to structs around.In practice, like in C++, you'd tend to only actually use the vtable approach for those things which you actually need to be able to override based on type, which tends to be rare.
Another approach is to use messages instead, and a call a generic message handler. E.g. "ob->call(MSG_TYPE, ... arguments)" and just have a pointer to a message handler function as the first pointer in your objects. It's more flexible, but slower.
Oh.My.God.
At this point, you might as well be using Java.
But of course if you're going to reimplement OO from scratch in C you will see the entire machinery laid bare. You're only doing it in the first place either because you're committed to using C for whatever reason, or to understand OO implementation strategies.
If you want simpler models see my other comments in this thread.
/gasp
Simple example here: https://embeddedgurus.com/state-space/2008/01/object-based-p...
Lots more here: https://stackoverflow.com/questions/351733/how-would-one-wri...
Embedding combined with the container_of-macro [0] is a much more constructive approach from my experience.
[0] https://stackoverflow.com/questions/15832301/understanding-c...
Charitability is not a tool merely for being nice to others, but being nice to yourself. You learn more this way.
>Classes in an object oriented design are fundamentally ADTs
It isn't. I would suggest the paper Object-Oriented Programming Versus Abstract Data Types by William R. Cook which explains the fundamental differences between the two data abstraction techniques.
edit:
https://www.cs.utexas.edu/users/wcook/papers/OOPvsADT/CookOO...