It happened a couple of times recently, that I have heard or read the discussion on when to choose C++. Opinions vary a lot. You can hear that C++ is a general use language and modern C++ is enough for any project as well as C++ is legacy forget about that. Of course, the truth lies somewhere in between, so I would like to have a closer look into that problem.
This overview will not be very technical though, If you do not have in-depth knowledge about the C++ language, then it is still fine. The article is meant to be readable also to the management people, to help them understand the rationale behind choosing that language.
What is C++?
Let’s start with what C++ is, what does C++ want to achieve and how does that affect the language itself.
C++ language is quite a unique being. We can go extremally low and high in the abstractions in the same program. We can directly influence the generated assembly code (something that is understandable to the machine) as well as create high-level frameworks. It’s also important to note, that C++ is a compiled only rather than interpreted language, meaning, that C++ needs compilation phase.
The ultimate goal of C++ – Backwards compatibility
Some may think, that C++ greatest strength is its performance. This is the standard argument behind choosing that language. This is not totally true, though. The truth is, that C++ was designed with backwards compatibility in mind and it’s still one of the most (if not single most) important features of the language.
Very first versions of C++ were supposed to be compatible with C language. Even now, after so many years of the C++ language, we have got a monstrous amount of C legacy stuff, that we maintain to be compatible with C language. C++ standardisation committee is also reluctant to any ABI breaking changes even if we could gain performance or improve the consistency in the standard libraries.
Even though backwards compatibility is a painful feature, it has great business value. This is backwards compatibility, that made C++ language so popular and widely used. You can also ship libraries in binary formats without sources and have confidence it won’t be broken anytime soon.
Having said that, C++ often sacrifices performance gains for backwards compatibility. This is especially true from the implementations (compilers/stdlib) point of view since they refuse to make changes in favour of performance when the cost is ABI incompatibility.
Ok, so how does that affect your business and language of choice? It’s quite simple if you intend to run a long-running project, and you are going to ship the binaries/libs to the client it is often important for you to stay backwards compatible (even on ABI level).
How does backwards compatibility look like in other languages? In case of Java – Java seems to be backwards compatible on the language level, on the library level it’s a bit different story since it’s known that Java tends to deprecate stuff from time to time (e.g. Date library) (C++ does that too). Also on the ABI level, Java is compiled to the intermediate format, and as such it’s much easier to maintain backwards compatibility in the language this way. Another competitor – Rust isn’t backwards compatible especially on the ABI level. Rust is a new technology and as such, they do not yet provide ABI level backwards compatibility. [Note: I am no Java expert, and so when it comes to non-C++ languages I might be wrong]
Performance and memory usage
This is the most difficult part for me to describe. I find this hard to make a reasonable comparison of the performance of two languages. The reason for that is, we would have to compare two production-level applications, that provide exact same functionality written in two different languages. And even then the skill and knowledge of the developers and time available to write such software would matter. Comparison of small programs or small algorithms is not reliable because it suffers from all the issues, that microbenchmarks have. As such I do not believe there is a good way to measure the performance of the language itself. But it wouldn’t be honest if I didn’t mention, that C++ is considered a very fast language. Java, on the other hand, was considered a heavy, slow language. Since then it improved a lot and I don’t believe we should criticize its performance anymore.
So I will just focus on what is known for granted and that can be proven. One thing, that can be said about the performance and memory usage of the C++ language is, that it is predictable.
This is caused by a couple of factors. First of all, C++ must work also for embedded devices with limited CPU power and memory. This means that produced binary should not require any additional runtime mechanisms (virtual machine being in my mind). This also means that dynamic memory allocations shouldn’t be required from developers if it is not necessary.
Having said that, developers can fine-tune allocation mechanisms to their needs or even work in environments, where dynamic allocation isn’t possible. Also, C++ can make a good job in working on hard real-time devices, where managed languages aren’t a good fit at all.
Also, my opinion about performance and memory management is, that it always should be more or less predictable. Memory usage unpredictability caused by virtual machines is irritating for software like IDEs or more resource-heavy desktop apps for a simple reason, the apps need a lot of memory and freeing a lot of memory takes time. When garbage collector kicks in for the large projects you can be sure your app will freeze.
You should also consider non-managed languages if you have got any requirements for the performance. Garbage collector, even though is customizable, is not that flexible as manually managing memory. Garbage collector eventually will kick-in and will dramatically slow down your app or freeze it completely.
Other languages that can compete with C++ in this field is, of course, Rust and Ada.
With great power comes great responsibility and flexibility in memory management causes the language to be very insecure. In case of managed languages running on the virtual machines, it’s a virtual machine that takes care of your memory.
When you manage memory on your own, bugs like double free, memory leaks, or invalid memory access can easily become an issue, that crashes your program or in the worst case becomes a security issue. Even though C++ has destructors and popular RAII idiom it’s not enough for C++ to be safe language. You still can easily cause invalid memory access, if you happen to access object outside of its lifetime for example (which isn’t that difficult after all – see the previous post here).
There is another positive aspect of C++ – RAII. Even though garbage collector can save you from leaking the memory you can leak other resources too. In this case, C++ wins the battle. C++ cannot guarantee, that every resource will be freed appropriately, but correct use of RAII pattern makes it likely to happen.
Another thing – undefined behaviours. C++ language is full of undefined behaviours. No one even remembers why some of them exist. C++ committee is currently working on reducing its number but there’s a lot work to do yet. What can happen, when a developer writes code with undefined behaviour? It’s undefined by definition, but varies from it will do the thing, developer intended to do up to crashing your program or simply behaving incorrectly. Undefined behaviours can also be a root cause of security issues.
Therefore, if you decide to use C++ language, make sure you have got enough C++ experts in your team, and moreover, make sure to extensively test your software with sanitizers. Those are the only ways, that will prevent you from performing nasty, hard to debug bugs and security issues.
Tooling is another part of the language, that is far from being perfect, but things are changing quite rapidly in this area. We will not have as great package managers for instance as python or we will not find any utility to help with project setup, nor we will find an out-of-the-box testing framework.
And build-systems… they are far away from being intuitive and simple. As for now, the industry standard is CMake. Looking at the CMake from the language point of view makes it feel older than bash itself. I dare to claim, that most C++ developers (from my experience – not data proven) do not know CMake good enough to be able to efficiently use it. There is also Meson out there, but it’s still young, I didn’t use it too much, so there is not much I can say. Build systems in C++ are far away from what is available right now in other languages.
At the same time, it’s worth noting, that build systems usually support many compilation options, so tooling for C++ must be more complex to handle that. Regardless of that, we right now have Conan, vcpkg, hunter and other package managers, that will do their job.
We also can live without out of the box provided testing framework. There are plenty out there on the market.
We also have nice tooling in the field of testing. We can test for memory leaks, undefined behaviours, multithreading issues etc. This can significantly improve the development process. At the same time, it’s worth noting, that some languages do not need to have some parts of sanitizers. For example, Java doesn’t need undefined behaviour sanitizer (as it does not have that many undefined behaviours) and also it doesn’t need memory leak detectors since memory is managed by the garbage collector there [Note: as pointed out by the reader in some rare cases even garbage collected languages can leak memory and so leak detector can be useful]. One thing to note – Rust is on its way to also support sanitizers – cool!
C++ is a total lame if it’s about the difficulty of learning the language. The language has a steep learning curve. There are many reasons for that. First of all is backwards compatibility. Any time something new is added to the language, it needs to be backwards compatible. Because of that, there are some quirks and corner cases in the language.
Secondly, the age of the language and its fast development. By that I mean, there are only a few languages out there, that are as old as C++ and at the same time going through so many feature changes. It’s like a big software project, where you cannot change the API. If you make a mistake and there is someone who actually uses that behaviour, you no longer can change it back. As time goes by you will end up having weird corner cases in your project, that you are unable to fix. It’s the same regarding the C++ language.
The last thing I consider troublesome is manual memory management. And no- unique_ptr and RAII are not the ultimate solutions to memory management. In simple cases, you can stick to unique_ptr. For more sophisticated ones you will need to carefully design your program. Shared_ptr is also not a solution to manual memory management since it introduces even more subtle issues with cyclic dependencies. In other words, you should always have a good reason to use shared_ptr in your code, rather than (what I commonly see) use shared_ptr everywhere.
If you are wondering how language difficulty influences your project, then there are two ways. First, you will have an issue of finding right candidate with proper knowledge about the language. Second, you will either spend more money looking for knowledgeable people about the language or you will spend more money fixing bugs.
Diverse third-party software
The age of the language has it’s own benefits. C++ is a popular language and it’s an old one. You will have plenty of libraries available out there in the market that will solve the issue you need help with. Usually, those libraries will be quite a high quality because there was plenty of time for hardening them. If for some reason, there is some kind of C library, that doesn’t have its wrapper already written somewhere, then you can easily wrap it yourself since C++ is C compatible.
By having a wide range of libraries available to use, you can gain speed of building an app. The more we do by a third party, the less time we need to spend on writing functionalities ourselves. Also, we save time on doing tests since third party libraries are usually well tested.
Wide range of supported hardware.
C++ prioritizes the possibility to be run on almost any hardware. That being said, if C++ is to deprecate any feature, it first makes sure, that no hardware will be affected by that change. For example, C++ decided only recently to stop supporting hardware, that is not using U2 encoding and broke programs, that use digraphs feature.
Ok, so should I choose C++?
We know the characteristics of the C++ language. Let’s now answer the question of when to choose C++.
So the first question you should ask yourself: Do I have performance requirements and how strict they are?. For real and hard real-time software, using a non-managed language without garbage collector is a must. You need to control everything in order to fill the predictability requirements. It doesn’t yet mean, it must be the C++ language, but it reduces your set of possibilities to C, C++, Rust, Ada (and probably few others).
Another question would be Is my application memory/CPU heavy? or Is my environment resource-constrained? Yet another one would be would be Can you save much money by reducing CPU/memory usage. If an answer to any of those questions is positive, then you should choose the language, that is performance proven and most probably, that is non-managed and non-garbage-collected.
Are you working on unique hardware? Choose C or C++. If there is some custom hardware, usually it will come with support for C and C++ language.
Are you working on a library, that is supposed to be backwards compatible most of the time. You might think about C or C++.
For any other reasons, C++ is probably not a good choice for you. Also, if you are writing an Android or an iOS application, then most probably you should stick to the default environment language to develop this app, even though the platform is constrained.
There is one more thing when choosing the language. How do I choose between C++ and other natively compiled languages?
Use Ada for specialized software. Ada is usually used in Aviation. Ada, despite its age, is quite a modern language (with many features years ahead of C++). You might find difficulties finding help or third-party libraries because Ada is simply not popular. It will be extremally difficult for you to find an Ada developer – be aware of that.
When choosing C versus C++ you should consider the following aspects. C++ is much more complex (yet much more powerful) language, again it’s always useful to have some language expert on the board. Some extremally rare hardware will come shipped only with C compiler. If that’s the case you have no choice. With uncareful developer and constrained environment, you might encounter issues like code bloating and binary size growth in case of C++ language.
And the final one Rust vs C++. One thing to consider is that Rust is a relatively young language. It still isn’t as stable as C++ (especially on the ABI level, but there are plans to change that!). There might be a lack of third party libraries on the crates.io. Otherwise, if you do not care that much about backward compatibility and are fine with lack of third-party libraries, then stick with Rust. You might actually want to use Rust even if you are worried about mentioned issues. Rust gives something else – safety. Rust’s borrow and lifetime checkers are able to prevent you from doing lots of errors in the field of memory management or data races. So I would say, unless you cannot, stick with Rust.
Consider C++ for:
- embedded development
- resource-constrained environments
- applications with heavy memory usage
- applications with more or less strict performance requirements
- heavy application, where their performance directly relates to costs
- when you heavily need to use reliable third-party libraries to accomplish your task
- when, for any reason, you care about backward compatibility (e.g. long running projects)
Otherwise use other languages.
Whenever writing an app, some kind of analysis should be done on what language to choose. The market is full of choices and every technology serves different needs. C++ is especially not efficient for writing prototypes, applications with small resource usage and mobile apps, but is useful when it comes to fine-tuning performance and memory usage of your program. Also, you will have a hard time programming embedded/bare-metal devices with a different language than natively compiled one.