Hacker News new | ask | show | jobs
by chrsw 473 days ago
I've noticed many large C projects resort to these sorts of OOP-like patterns to manage the complexity of the design and size of the code base. But I'm not aware of any one standard way of doing this in C. It seems C++ standardized a lot of these concepts, or C++ developers adopted standard patterns somehow.
2 comments

> I've noticed many large C projects resort to these sorts of OOP-like patterns to manage the complexity of the design and size of the code base

The Power of Interoperability: Why Objects Are Inevitable

https://www.cs.cmu.edu/~aldrich/papers/objects-essay.pdf

Objects regardless of what shape they take, are basically an evolution of modules that can be passed around as values, instead of having a single instance of them.

That is why they are here to stay, and even all mainstream FP and LP languages offer features that provide similar capabilities, even if they get other names for the same thing.

It is like saying an artifact is useless, only because it get named differently in English and Chinese.

and C++ also supports optimizing them, especially when you use `final` keyword and LTO which is able to devirtualize at the scale of a whole program.
Interesting, in Rust those optimizations are more implicit since there's no "final" keyword when you use dynamic dispatch via trait objects. + you also got LTO.

I wonder if there are many cases where C++ will devirtualize and Rust won't.

But then again Rust devs are more likely to use static dispatch via generics if performance is critical.

In Rust objects can dynamically go in and out of having virtual dispatch. The vtable is only in the pointer to the object, so you can add or remove it. Take a non-virtual object, lend it temporarily as a dynamically dispatched object, and then go back to using it directly as a concrete type, without reallocating anything.

That's pretty powerful:

• any type can be used in dynamic contexts, e.g. implement Debug print for an int or a Point, without paying cost of a vtable for each instance.

• you don't rely on, and don't fight, devirtualization. You can decide to selectively use dynamic dispatch in some places to reduce code size, without committing to it everywhere.

• you can write your own trait with your own virtual methods for a foreign type, and the type doesn't have to opt-in to this.

> But then again Rust devs are more likely to use static dispatch via generics if performance is critical.

Put another way, in C++ the dynamic dispatch is implicit, so you might write code which (read literally) has dynamic dispatch but the optimizer will devirtualize it. However in Rust dynamic dispatch is explicit, so, you just would not write the dynamic dispatch - it's not really relevant whether an optimizer would "fix" that if you went out of your way to get it wrong. It's an idiomatic difference I'd say.

> But then again Rust devs are more likely to use static dispatch via generics if performance is critical.

I'm not sure I follow - pretty much 99% of usage of C++ in the last, like, 20 years has been around making sure that you get static dispatch with polymorphism through templates. It's exceedingly uncommon to see the `virtual` keyword unless you have, say, some DLL-based run-time plug-in system going on.

yeah true, I'm really liking pornel's reply though on rust objects going between static and dynamic dispatch.