Right you are. Linus did a pretty good [well, succinct anyway :-)] job explaining this.
Here are a few more detailed reasons why you can't write a kernel in C++:
- C++ constructors [or destructors] can't return error codes. They can only throw exceptions.
- Leaving aside the fact that relying on exceptions (vs. checking error codes, which the kernel code already does at every step of the way) is largely unsuitable in a kernel [or an app even]
- The kernel has several modes/states: in ISR, in syscall, entering/leaving syscall, in kernel thread, in tasklet/ISR bottom half and most of those, if not all, an exception can't be used.
- Even you still wanted to try, the exception code that C++ generates can't be used in the kernel. It would have to be something custom.
- So, you can't wrap a lock acquisition inside a constructor [and release inside a destructor] because if you're in an ISR, you have to do "trylock" [and test the return code] instead of "getlock". Trying to wrap the trylock in a constructor requires that you be able to throw an exception.
- Nothing in C++ (inheritance, polymorphism, operator overloading, etc.) helps the kernel with the bulk of what it does (e.g. programming devices, ordering of FS writes with journaling, setting up page tables, etc.). A lot of things the kernel does are "dirty" jobs (e.g. some device interfaces are straight out of kafka, particularly for older devices).
- The linux kernel [and *BSD] are shining examples of how to program in C in a "clean" way, despite having to do a "dirty" job
- As to "object oriented" programming, the kernel already does a fair bit of it already, but without the fanfare/hoopla.
When Linus first introduced git, he gave a video talk. He said [paraphrasing] "It's all about the merging, stupid". To me, it's all about the debugging
Disadvantages of C++ over C:
- constructors can't return error codes, only throw exceptions. In C, you can choose what ever you want:
int errcode = alloc_and_construct(&new_ptr);
foo_t *new_ptr = allow_and_construct(&errcode);
foo_t *new_ptr = alloc_and_construct();
if (new_ptr == NULL) // error ...
- new/delete are operators and not functions [defective by design]. See http://www.scs.stanford.edu/~d... for a far better/in-depth explanation/condemnation than I could give.
- templates and stl -- they can simplify some code, but if the stl implementation has a bug, where do you put the breakpoint? Of course, the stl is like "Westworld"  "Where nothing can ever go wrong ... go wrong ... go rong ..." :-)
- Try to explain to your boss that the reason you can't ship a product is because you used the stl heavily and you'll have to wait six months before the bug fix gets propagated to all the platforms you ship on.
- A simple "x = y" can generate a copy constructor [or two other things that I can't remember]. Trying to decide which one gets generated "at a glance" is problematic. The simple line may generate a lot of code that is slow. In C, there's little to no ambiguity (e.g. x/y are either simple types (e.g. int), pointers [to structs], or structs and the execution time is more easily predictable). There was a proposal a while back to come up with a C++ subset for realtime [IMO, why??? If you want realtime, just use C]. The one feature I remember was removing copy constructors [as evil].
- In C++, if you're trying to use some of the more advanced/powerful features, it often takes longer to get it to compile and ensure correctness. This is pure overhead to actually shipping code. You spend more time haggling with the language than the problem itself. And, anybody taking over maintenance of the code will have the same learning curve.
- In most of the code I've read, C++ is more sparsely commented than C code. IMO, partly because after all the "haggling", you're exhausted, and still don't understand how the stl/whatever actually works, so your only comment would be "stl magic that I don't understand" [which most people would leave out]. A genuine frustration for programmers doing maintenance and bugfix because they have to cast a wide net looking for the source of a bug and may not be able to spend the time on the "cute construct" you used.
- In a C programming class, you're doing exercises to implement linked lists, trees, dynamic arrays [using realloc], etc. Or, these are covered in a follow on "data structures" class. Unless a C programming class is a prereq for a C++ programming class, there is too much material, and you're going to be taught to just use stl::whatever instead of implementing your own and then being shown the stl equivalent. So, you're highly dependent on using something that is an ever increasing, hard to debug, bit of standard functions, and because you never learned how to implement them yourself, you're beholden to using them, no matter what the bugs and side effects are.
- Polymorphism actually makes code harder to read. I once did a portion of a realtime project, written in C, in C++, just to see if it could be beneficial to use more C++. I had a polymorphic function "foobar". Afterwards, both my boss and I concluded that it was clearer to use "foobar_int" and "foobar_float" because you didn't have to keep looking for the argument definitions to get their type and figure out which polymorphic version of foobar would need to be breakpointed.
- Inheritance is [wildly] overused. The acid test is the "is a" vs "has a" decision. Most real world data relationships are "has a". But, in many cases, newbies feel they have to use inheritance, otherwise, they feel they're "not real C++ programmers"
- Inheritance violates encapsulation. If you have three classes X, Y, and Z (X is the base class, Y inherits from X, and Z inherits from Y), and each has various fncX1/fncX2/..., fncY1/fncY2/..., and fncZ1/fncZ2/... Now, you instantiate class Z. Then, when you use Z.fncX1 you're violating encapsulation because you're having to have [incestuous] knowledge of how Z was implemented in order to know that is has [by virtue of a two level inheritance from X] that Z.fncX1 is valid. Imagine a five level hierarchy. Now, you're probably looking through five different .h files and possibly five different .cpp files just to find the function definition/body of fncX1.
- Inheritance is even worse. In the above X/Y/Z example, suppose fncX12 is a public function that was added to the X class after class Z was created. The creator of Z never intended users of Z to do Z.fncX12 because it was unknown at the time and couldn't be warned against. Now, Z is changed to no longer use X/Y. Now, Z.fncX12 no longer exists. Suppose, that Z only wanted to use fncX3 and fncY7 and thus inherited from Y. But, the simplification of Z to provide fncX3/fncY7 functionality directly [as fncZ3/fncZ4] now breaks code. This was all done because X/Y were not under the control of the author of Z. Bad, dog, bad. Bad ...
- The RTTI. Most companies disallow it because it is very slow. Now, when doing a [yecch] pointer "downcast" (from base class to child class), instead of doing <dynamic_cast>, you're doing <static_cast> with the hope your program doesn't segfault.