Hello,
I will focus only on embedded C++ (mainly on STM32 for my experience), for PC development I use mainly C#.NET.
From my point of view there is 3 categories of persons about this subject:
1 - people who will not use C++, ever, because they love C, they love the way they work (forgive me but I depict them as using VI for code editing, launching compiler with command line and debugging with LEDs/UART ... a bit caricatural I know, but sometime not so far from reality). I won't try to convince them to switch to C++, not even to try it out, because they are convinced C++ is bloaty, that OOP leads only to bad code, and technical reasons so out of date or utterly wrong (no an object does not have pointers on all it's members functions ... it MAY have a vtable for certain virtual methods, depending on the compilation options and many other parameters) that it's not even a debate, it's talking to walls.
2 - people who already use C++ when they can. They already know why they use it, so I guess we can share ideas, tips and tricks, but I don't have to convince them anymore.
3 - people that use C because "isn't it what people use for embedded ?", who already use "modern" tools (IDE based Eclipse or visual studio, a real debugger with fancy options like stepping, breakpoints, memory analyzers, etc.), and who wander why would they use C++ in addition to C. Those are my target today, if I do my job correctly they will try it, maybe not like it and revert to C, but I'm pretty sure some of them will discover a new world of possibilities.
That being said, why and more importantly HOW would you use C++ for embedded development ?
First you have to understand language is only a tool. You can do wonders with inadequate tools if you are very experienced, but a good tool makes the job easier.
Reminds me a cartoon I used to read when I was young "Leonard : genius", where he sees woodcutters using a manual saw to cut a tree, he first give them a chainsaw but forgot the gas to put in, so he go get some gas and when he come back the woodcutters are using the chainsaw as a manual saw, complaining it is heavier and does not cut as well as their manual saw ...
That's the main point for C++, it's a very efficient tool if you learn how to use it, and use it right :
- C++ is not OOP C
- embeddded C++ is not desktop C++
- I'd say there is not one C++, there are as many C++ as developers or teams of developers using it
What we don't use in embedded C++:
- exceptions : an embedded program is often structured as "init then while true do always the same thing again and again", you should handle error cases, not rely on exceptions that are useful only on high end OS like windows or linux.
- reflexion : same reason, in embedded you don't load code dynamically, everything is known at compile time, so there is no use for reflexion (RTTI or Real Time Type Information)
- dynamic allocation : again, almost everything is known at compile time, and memory is very limited compared to a PC, so almost everything should be statically allocated, and data with unknown (but bounded) size be allocated in specific, dedicated buffers
Most of the embedded C++ tools I know of disable exceptions and RTTI by default, so you don't have to care about it. Dynamic allocation is permitted by default, our framework overrides the default "new" with an error, so that you know you should not use the default new (you can still override it for specific purpose).
Encapsulation is the ability to specify what part of your object is accessible from the outside. If you look at a C struct, what field can I change directly, what should be changed by using a function (and where is this function's code anyway ...) ? In C++ you describe your object, it's inner working, and the interface, i.e. how should people or other objects interact with your object. When you work as a team, and you need to code some classes for someone else, you first define the interface, it makes things so much easier, and then you can change the inner working of your class, he can change how he uses your class, without any breaking change. C has everything accessible, so either you are working alone and know what to do and not to do, or you will need to write extensive documentation about how to use your struct, when you can change the fields directly or need to go through accessors, etc.
Because of the dynamic allocation, you also will not use many of the provided STL data structures because they are targeted at PC development and heavily rely on dynamic allocation (yep, no vector allowed here ...).
But the good thing is you will write your own data containers, once. Want to use linked lists ? Write a template, debug it and you're done, no need to write it again.
Templates gives the ability to describe features or concepts, that do not depend on the underlying type : how a list works does not depend on the list item type ... an integer list works the same way as a float list, or a list of objects.
Code once, debug once, use it for free for the rest of your life.
Virtual methods are cheap performance wise. Worst case it is an additional dereference (through the vtable), but in many case the compiler will know at compile time what is to be called and will optimize it for you. What are virtual methods ? A way to link an object to a specific member function (in case two objects should call different code for the same method), of course you can do it in C (there is nothing, output wise, that can be done in C++ that cannot be done in C), but then you will have to handle the vtable yourself, update it, make use of it manually, etc. C++ compilers does the same, except with a simple keyword you can tell it to do the work for you. You may not understand why it is so useful right now, but there is a point where you face a problem, and having a pointer to the code in the object itself is the solution ... C++ already have the tools available for you.
C++ is a modern programming language. There is C++17 (2017) currently implemented in most compilers, and C++20 (2020) will probably have concepts, which will be awesome. C++ improves on new features, sure, but also a lot on making it easier and simpler. C ... well, last C standard is C99 ... yes, 1999.
In many case, simple C++ is simpler and faster than C. Ok let's take a very simple exemple, I want to create a buffer of 30 bytes and fill it with a specific value (say 0x55), let's see how to do it in "simple C" (some would say simplistic C), in "simple C++", then in "advanced C" and "advanced C++":
Simple C:
uint8_t buffer[30];
uint8_t i;
for(i = 0; i<30; i++)
buffer
= 0x55;
Simple C++:
std::array<uint8_t, 30> buffer;
buffer.fill(0x55);
Let's compare them:
- lines of code : 4 lines vs 2 lines. Well yeah you could
- readability : yeah a simple for loop is not that hard to understand quickly for an experienced developer ... but agree that buffer.fill is more expressive, no ? You don't have to understand what it does looking at the code, it's self-describing ...
- performance : I tested it on Atollic, running it on the actual STM32, the C++ version is MUCH FASTER than the C version. Why ? Because by default array.fill can optimize much more than the for loop (STM32 is a 32 bit platform, so fill will transfer 32 bit at a time instead of 8, and also move multiple 32 bit registers at a time to maximize throughput).
- bug-prone : how many times does C++ repeat the buffer size ? None. Whereas the C code duplicates the "30" magic number, so there is a chance you change the buffer size later and forget to change the for loop condition, ending in memory corruption. Ho and by the way, what happens in C code if I change the buffer size to 300 ?
- memory : it is the EXACT SAME result on both, they use only the required memory space, that is 30 bytes. But then you will ask, how the hell does fill knows the buffer length ? It is a sort of "compiler only" variable, that the compiler know, but that will not take space in memory if not needed.
Now I hear some C dev shouting at me, saying that is not good C and nobody writes such code. From my experience, the vast majority of C developers write this type of code.
But let's see the "advanced" version, something a more experienced C or C++ developer would probably write:
Advanced C:
uint8_t buffer[30];
memset(buffer, 0x55, 30);
Advanced C++:
std::array<uint8_t, 30> buffer;
buffer.fill(0x55);
Let's compare them:
- Yes, read it again ... the "advanced C++" is the exact same as "simple C++", I even copy-pasted it to make sure it was exactly the same.
- C is now 2 lines of code, same as C++
- readability : well, memset of not that bad, let's say it's a tie even if I think fill is more meaningful.
- performance : I'll be honest, from my tests C was just a bit faster (a few percent at the very best) depending on the compiler options. So that's a point for C, that I would mitigate because on the simple case C was many times slower than C++ (not a few percent), and just because you still can write the C version in C++ and get the same performance in case you really need the extra clock cycles (but then I'm pretty sure you can optimize a lot more somewhere else in your code...)
- bug-prone : C still repeats the buffer size, but at least memset uses a 32 bit index by default
- memory : same as before
What I want to show you is C++ is easier to write "good / not so bad" where beginner C developers often use bad behaviors. Why ? In C you have to search for the memset function, understand how it works, if you don't know it exists then you will use the for loop. In C++ if you have the right tool, you just type ctrl-space and you are presented with the possibilities ... there is a fill function on a buffer ? does it do what I expect (you still need to write documentation, but far less) ? yes ? then just use it and get 98% of the performance I'd get using advanced C (and many times faster than simplistic C).
Also not repeating the buffer size is one of the major, if not the best, feature of C++. Contrary that what some may say, C++ is not bloaty but it has plenty of tools to make lighter code size / memory footprint / code performance.
One of them is the constexpr keyword, constexpr tells the compiler it may have everything at hand to compute the result of some arbitrary code, and then it should do so (compute the result at compile-time, not at runtime).
Compilers do that kind of optimization for years, even in C, but you have absolutely no way to tell the compiler you want it to be that way. With constexpr you can both ask the compiler to compute at compile-time, and ask it to enforce that it can compute it at compile time (so that you cannot modify the code in a way it's not anymore and you loose runtime performance without knowing why). What if you don't use constexpr in C++ ? Well you get the same behavior as C, if the compiler can optimize, it will ... maybe, maybe not.
And constexpr variables do not use memory in the final binary code !!!! You can define HUGE constexpr variables in embedded code, it will not use any of your few kB of RAM, it will only use a small portion of your multi GB PC RAM.
So what you will be using extensively when switching from C to C++ in embedded projects:
- constexpr to make as much as possible your PC do calculation instead of your small microcontroller.
- templates to reuse data structures as much as possible, writing less code (and less code means less bugs)
- public / private to show only what you want of your object
- at some point you will probably face problems that have good solutions built in C++ (virtual methods, std::function, lambdas, etc.) At least as good as what you would do as an experienced C developer, except the compiler does part of the job for you, if you drive it correctly.
Here in my team we develop for embedded in C++ for a few years. We developed our own in-house RTOS for Cortex-M(3/4/7), our own framework of data structures, drivers and devices that we can reuse on all projects.
We have been invited in the ST partner program last year and we are in tight discussion with ST about this subject, we plan to make seminars/webinars on this subject too (at first in French, sorry), and they are still amazed at the performance we get out of their controllers, even compared to very experienced C programmers/teams.
And ask my team members about it (@shangaoren is one of them), they really embraced the C++ way of thinking and clearly see why we use it.
If I managed to get you interesed in the subject (YES !), then here is a few resources that you may find vey helpful:
- https://godbolt.org/ compiler explorer, this tool is so nice to understand what the compiler does under the hood, and it includes ARM Cortex-M compilers. So helpful to understand the effects of optimization flags, etc.
- about constexpr, and especially this one shows C++ is also targeted at very small platforms with efficiency in mind.
- The cppcon talks : https://www.youtube.com/results?search_query=cppcon some of them are mainly/only targetted at PC development, but a vast majority are applicable to embedded development.
- Jason turner youtube channel : https://www.youtube.com/channel/UCxHAlbZQNFU2LgEtiqd2Maw with C++ weekly, these are short technical videos on specific C++ aspects.
Let me know if you need more, or want to discuss specific aspects/features of C++ for embedded.
Thomas.