Sure I understand all of that, and I'm really surprised you thought I don't.

You describe the essence of OO being implemented by vtables and how C++ does the right thing while many Unixy kernels do this by hand.

(I understand the part about manually set up vtables as well, and in fact one of the things I do for money is maintenance of a commercial Linux kernel module that has lots of these. I don't feel like Linux has a problem encoding these tables by hand, while it is one of the projects that has the most need for vtables because it fits the description of a system that needs to be very flexible. Linux has many more relevant problems than hand-crafted vtables.)

Maybe it will surprise you, but for the greenfield stuff that I do in my spare time and that I'm allowed to do as my job, I can write dozens of KLOC of code without ever needing to setup a vtable. This probably isn't even that special for how I approach things - if we go to any random C++ class, chances are that by far the most methods there aren't virtual, or at least are prematurely virtual.

Personally I think of vtable situations as less than ideal from a technical perspective. I get that these situations can arise naturally in highly flexible systems where there are lots of alternative implementations for the same concept. On the other hand, I think vtables are problematic as a default, in particular for abstractions that aren't yet proven by time to work. They tend to create this kind of callback hell where stuff never gets done in the most direct way, and where you end up with much more repeated boilerplate than you'd like.

There are many other ways to go about "interfaces" that are not vtables, and it depends on the situation which is best. Vtables are callback interfaces, and I try to avoid callbacks if possible. As described callbacks make for incoherent code, since the contexts of the caller and the callee are completely disjoint. Another problem is that they imply a tight temporal coupling (a callback is a synchronous call)!

I would say the primary approach that I use where vtables are maybe often advertised, is to decouple using queues (concurrency, not necessarily implying parallelism). This achieves not only decoupling of implementation but also temporal decoupling. Asynchronicity should never replace standard function calls of course. But it is a great way to do communication between subsystems. Not only for requests that could take a while to process (parallelism) but also from a perspective of modularity and maintainability.

read() and write() are the best examples, they are now starting to be replaced by io_uring in the Linux kernel. I think read() and write() maybe only ever made sense in the context of single-cpu machines. Doing these things asynchronously (io_uring) makes so much more sense from an architectural perspective and also from a performance / scheduling perspective if requests aren't required (or impossible) to be served immediately.

Leaving that aside, there are other ways to do vtables than the one C++ has baked in. A few months ago I watched a video about this by Casey Muratori that I liked, but I can't find it right now. He talked about how he dislikes exactly why you stated, because it promotes one way to "implement" OO (which might not be the best, for example because of double indirection) over others.

Regarding (RAII) destructors, it's a similar situation. If I need a language feature that helps me call my destructors in the right order, this could mean I have too many of them and lost control. I can see value in automated destruction for script-like, scientific, and enterprise code, and admittedly also for some usage code in kernel / systems programming a la "MutexLocker locker(mutex);". But as code gets less scripty and more system-y, the cases where resources get initialized and destroy in the same code location (scope) become fewer. I have decided to see what happens if I leave destructors out completely and try to find alternative structures, which I feel has been fruitful so far.