This technique predates object oriented programming. It is called an abstract data type or data abstraction. A key difference between data abstraction and object oriented programming is that you can leave functions unimplemented in your abstract data type while OOP requires that the functions always be implemented.
The sanest way to have optional functions in object oriented programming that occurs to me would be to have an additional class for each optional function and inherit each one you implement alongside your base class via multiple inheritance. Then you would need to check at runtime whether the object is an instance of the additional class before using an optional function. With an abstract data type, you would just be do a simple NULL check to see if the function pointer is present before using it.
It's sad that OOP was corrupted by the excessively class-centric C++ and Java design patterns.
This introduces performance issues larger than the typical ones associated with vtable lookups. Not all domains can afford this today and even fewer in the 80s/90s when these languages were first designed.
> It's sad that OOP was corrupted by the excessively class-centric C++ and Java design patterns.
Both Smalltalk and Objective-C are class based and messages are single-receiver dispatched. So it’s not classes that you’re objecting to. It’s compile-time resolved (eg: vtable) method dispatch vs a more dynamic dispatch with messages.
Ruby, Python, and Javascript all allow for last-resort attribute/message dispatching in various ways: Ruby via `method_missing`, Python by supplying `__getattr__`, and Javascript via Proxy objects.
smalltalk is not original OO - c++ took oo from simula which was always a different system.
Often class-based programming is confused as being the only style of OOP, superior to all other styles, or heavy-handedly pushed on others. Many programmers are perfectly fine with using objects or only specific features of OOP, without classes, if they are "allowed" to.
In the Linux VFS for example, there are optimized functions for reading and writing, but if those are not implemented, a fallback to unoptimized functions is done at the call sites. Both sets are function pointers and you only need to implement one if I recall correctly.
I would rather say that OOP is a formalization of predating patterns and paradigma.
For full disclosure, I have never verified that leaving ->read() unimplemented when ->read_iter() is implemented is safe, but I have seen enough examples of code that I strongly suspect it is and if it is not, it is probably a bug.
source- I wrote a windowing framework for MacOS using this pattern and others, in C with MetroWerks at the time.
As for abstract data types, they originated in Lisp, which also predates object oriented programming.
(sorry it took more than a decade for Java to catch up and Sun Microsystems originally sued Microsoft for trying to add lambdas to java way back when, and even wrote a white paper insisting that anonymous inner classes are a perfectly good substitute - stop laughing)
class DefaultTask { }
class SpecialTask { }
class UsedItem {
UsedItem() { _task = new SpecialTask() }
void DoIt() { _task.DoIt() }
}Is python a OOP language? Self / this / object pointer has to be passed similar to using C style object-oriented / data abstraction.
The difference is that design patterns are a technique where you use features not implemented by the compiler or language, and all the checks have to be done by the developer, manually.
Thus, you are doing part of the work of the compiler.
In assembler, a function call is a design pattern.
No need to pass in the object explicitly, etc.
Doesn't have the greatest documentation, but has a full test suite (e.g., [1][2]).
[0] https://github.com/peterpaul/co2
[1] https://github.com/peterpaul/co2/blob/master/carbon/test/pas...
[2] https://github.com/peterpaul/co2/blob/master/carbon/test/pas...
0. https://github.com/peterpaul/co2/tree/master/examples/my-obj...
I personally don't like implicit this. You are very much passing a this instance around, as opposed to a class method. Also explicit this eliminates the problem, that you don't know if the variable is an instance variable or a global/from somewhere else.
(- this->b + sqrt(this->b * this->b - 4 this->a * this->c))/(2 * this->a)
and (- this->b - sqrt(this->b * this->b - 4 this->a * this->c))/(2 * this->a)
This is a readability problem for any class that is used to do computations.That would be a complete redability disaster... at least for C++. Java peeps probably won't even flinch ;)
object->ops->start(object)
Where not only is it explicit, but you need to specify the object twice (once to resolve the Vtable, and a second time to pass the object to the stateless C method implementation). #define CALL(object, function, ...) (object->ops->function(object, __VA_ARGS__)) object1->op->start(object2)
superclass->op->start(object)People typically use some kind of naming convention for their member variables, e.g. mFoo, m_Foo, m_foo, foo_, etc., so that's not an issue. I find `foo_` much more concise than `this->foo`. Also note that you can use explicity this in C++ if you really want to.
Ask how do I do this, well see it's magic. It just happens.
Something went wrong? That's also magic.
After 40 years I hate magic.
mystruct_dosmth(s);
mystruct_dosmthelse(s);
vs s->dosmth();
s->dosmthelse();For the function naming, nothing stops you from doing the same in C:
static dosmth (struct * s);
s->dosmth = dosmth;
That doesn't stop you from mentioning s twice. While it is redundant in the common case, it isn't in every case like I wrote elsewhere. Also this is easily fixable as written several times here, by a macro, or by using the type directly.That said, I like having a this pointer explicitly passed as it is in C with ADTs. The functions that do not need a this pointer never accidentally have it passed from the developer forgetting to mark the function static or not wanting to rewrite all of the function accesses to use the :: operator.
I wrote about this concept[1] for my own understanding as well -- just tracing the an instance of the pattern through the tmux code.
[0] https://raw.githubusercontent.com/tmux/tmux/1536b7e206e51488... [1] https://blog.drnll.com/tmux-obj-oriented-commands
OP doesn't use void pointers, he uses void. He writes about functions having no arguments and returning nothing for the same reason other blog posts name functions foo and bar.
> OP uses this vtable as a form of indirection to implement runtime method swapping and polymorphism
The kernel uses vtables to implement polymorphism, it doesn't store the vtable in the object to save space. If there is no polymorphism, you don't use a vtable at all, that's saving even more space.
Not true. You use a vtable even if there is no polymorphism, in cases where you want to have objects store pointers to their methods (OO interface), but don't want each instance to have pointers to all methods. I was referring to this article, which the OP links in his post https://lwn.net/Articles/444910/
It would be nice though, if syntax like the following would be supported:
struct A
{
int a;
};
struct B
{
int b;
struct A a;
};
void foo (struct A * a)
{
struct B * b;
&b->a = pa;
}
struct B b;
foo (&b.a);IMO it's much easier to write resleaks/double-frees with refcounted objects in C than it is in C++
The C syntax is not really that complicated. Dynamic dispatch and virtual methods was already in the article. Here is inheritance:
struct Subclass {
struct Baseclass base;
};
That's not really that complicated. Sure, you need to encapsulate every method of the parent class, if you want to expose it. But you are also recommended to do that in other languages, and if you subclass you probably want to slightly modify behaviour anyway.As for stuff like templates: C doesn't thinks everything needs to be in the compiler. For example shadowing and hiding symbols can be done by the linker, since this is the component that handles symbol resolution across different units anyway. When you want templates, either you actually want a cheap way of runtime dynamism, then do that, or you want source code generation. Why does the compiler need to do that? For the basics there is a separate tool in the language: the Preprocessor, if you want more, you are free to choose your tool. If you want a macro language, there is e.g. M4. If you want another generator just use it. If you feel no tool really cuts it, why don't you write your code generator in C?
I was involved in a product with a large codebase structured like this and it was a maintainability nightmare with no upsides. Multiple attempts were made to move away from this to no avail.
Consider that the code has terrible readability due to no syntax-sugar, the compiler cannot see through the pointers to optimise anything, tooling has no clue what to do with it. On top of that, the syntax is odd and requires any newbies to effectively understand how a c++ compiler works under-the-hood to get anything out of it.
On top of those points, the dubious benefits of OOP make doing this a quick way to kill long-term maintainability of your project.
For the devs who come after you, dont try to turn C into a poor-mans C++. If you really want to, please just use C++.
To me less syntactic sugar is more readable, because you see what function call involves dynamic dispatch and which doesn't. Ideally it should also lead to dynamic dispatch being restricted to where it is needed.
I don't know where (might also have been LWN), but there was a post about it actually being more optimizable by the compiler, because dynamic code in C involves much less function pointers and the compiler can assume UB more often, because the assignments are in user code.
> requires any newbies to effectively understand how a c++ compiler
You are not supposed to reimplement a C++ compiler exactly, you are supposed to understand how OOP works and then this emerges naturally.
> dont try to turn C into a poor-mans C++
It's not poor-mans C++, when it's idiomatic C.
People like me very much choose C while having this usage in mind, because its clearer and I can sprinkle dynamism where it's needed not where the language/compiler prescribes it and because every dynamism is clear because there is not dynamic sugar, so you can't hide it.