The core problem with embedded C++ is that exceptions (
try,
catch,
throw) are not supported, but in standard (hosted) C++ environments they are
the standard mechanism for error propagation. That is the major one, but there are additional minor problems like static object initialization order, and so on.
It is my opinion that because of those differences, standard C++ programming guides are not suitable as-is. I suppose one can pick-and-choose what to adopt, but the paradigm, approaching problems "in a C++ way", is different in embedded C++ than in standard hosted C++.
I've looked at the sources of many different embedded C/C++ open source projects, from SmoothieBoards to Arduino/PlatformIO libraries, and to me it seems that problems are approached from the "C way" (with respect to abstractions and how they are used), with freestanding C++ facilities like objects, templates, and atomics used rather like an extension to C. Note that my experience is limited to open source projects, and therefore is likely skewed; I too would be interested in the opinion of whether this applies to proprietary projects as well, by those who have used C++ in embedded environments in many proprietary projects.
If one were to assume a similar opinion to mine, then it might help to list some of the most useful features of freestanding C++ used in embedded environments. (I hope others will pipe up, because these are just the most important ones I've noted.)
- Objects, classes, and inheritance
While one can implement plain objects as structures in C, the class hierarchy and inheritance means you can move common functionality and properties to parent classes. For example, if you drive an ILI9341-controlled display, the commands and data stay the same, but the underlying bus can be 9-bit SPI, 8-bit SPI with a separate command/data output line, or one of multiple parallel bus types. If you implement a class for the display controller, and a class for each of the bus types, you can use multiple inheritance to create a class that inherits from both, and only contains the needed "glue" code.
If the class interfaces are well designed, the classes can be refactored (implementation rewritten without changing the interface) if needed, and overall the code can be easier to maintain and to port to different architectures.
- Function overloading
In C++, you can implement "versions" of the same function that only differ by the parameters they take. The compiler will then choose the correct one (at compile time), depending on the parameters passed. This simplifies the interfaces and reduces programmer cognitive load, because they then only need to remember that say send() object member or function is used to send data in various forms, instead of say send_string() for strings, send_data() for arrays with specified length, and so on.
- Templates
Basically, when you define a class or a function, you can leave out the types of certain parameters. When instantiated (used), the compiler will make sure that there is an implementation with the types specified at that point. This can be extremely useful, for example if one wants to implement say fixed-point arithmetic; but the downside is that each unique combination of the types usually requires a completely new machine code implementation, so the compiled binary size can grow unexpectedly large with careless use of templates.
Again, those are just the ones that have been obvious to me. I wonder if I should include atomics in there.. I didn't, because I've only ever used the GCC
atomic built-ins and only after verifying they don't compile to function calls (i.e., emulation via some kind of locking facility).
I don't remember seeing operator overloading used in a useful manner, and personally do not except when implementing an arithmetic class (say, fixed-point arithmetic, or range arithmetic). A lot of code (especially those targeted at AVR and ARM architectures) also happily use variable and function attributes provided by GCC and LLVM/Clang as extensions to C and C++. I don't know how much those are frowned upon in the proprietary world using non-GCC, non-Clang toolchains.
If one wants to be cruel, they could say I myself write embedded/freestanding C++ pretty much as I'd write in a language that was a bastard mix of Python and C. So take that into account when considering my opinions here.
Hopefully others will pipe up with their opinions and experiences. I might be completely wrong, and need a restart in C++ myself, too.