The term “Modern C++” is often used interchangeably with “Code using the new C++ standard”. Here, “new” may be anything from C++11 to C++17 or even whatever is available of C++20 right now. I think that modern C++ is more and something different than just adding that
What is modern?
If we look up definitions for the word “modern” on the web, one of the first is by Merriam-Webster. The two parts relevant for “Modern C++” are:
2 : involving recent techniques, methods, or ideas : (up-to-date) modern methods of communication
3 capitalized : of, relating to, or having the characteristics of the present or most recent period of development of a language – Modern English
When it comes to techniques, methods, or ideas, there’s much more than just new language features. Often, those new features support or enable such techniques, but others have been around for quite some time. Regarding characteristics of the development of the language, those characteristics arise from how we use the language. This is about how we put features – old and new – together, which is much more than just what is a valid C++ program and what comes with the standard library.
One could argue that the features that have been around since ’98 are not part of modern C++, because they have been around for so long. However, we have to keep in mind that those that are vocal in the C++ community and talk or write about “Modern C++” typically are part of the early adopters. A huge number of people still write, learn, and even teach good old 90’s “C with classes”, which makes many methods that are not used in that style part of Modern C++.
Beyond new features
So what are those things available in C++98 that I think belong to the “Modern C++” category? Here’s a (non-exhaustive) list of a few important features and ideas:
RAII stands for “Resource Acquisition Is Initialization”, or “Responsibility Acquisition Is Initialization”. While the name stresses the initialization part, the key aspect here is actually the destructor. Deterministic destruction is one of C++’s core features that sets it apart from most other languages. For many, it is the most important feature.
RAII can be used to reliably manage a plethora of things, like memory (e.g. in
std::string), file handles (
std::fstream), network handles, mutexes, database connections, but also things that are not exactly resources. If you ever have to do something and reliably undo it at the end of some scope or when some object lifetime ends, RAII is your friend.
I’ve seen lots of old code bases where functions would end in a mess of manual cleanup. That cleanup won’t be reached in case of exceptions, so RAII is your friend here. Even if you have exceptions turned off, early returns can clean up your code considerably, but not if you still have that cleanup to do.
So, RAII definitely belongs to modern C++ – even if it has been available since the very beginning.
The idea of strong typing has been all the rage recently. In the old days, every ID, size, Zip code, price etc. was just an int or double or another arithmetic type. That they are compatible with other, completely unrelated values that happen to share the same type was a pity and a source of bugs, but what can we do? At least the compiler doesn’t silently cast numbers and arrays to strings!
It turns out that with C++’s type system and the zero overhead abstractions* brought to us by the compiler, we can do a lot. Simply create different ID types, ZipCode types, size types (no, not typedefs, thank you) etc. If you’re interested, watch one of the talks by Björn Fahller, Jonathan Boccara or Jonathan Müller.
*(Even if such an abstraction is not entirely zero overhead, prove that the overhead hurts before you dismiss it.)
Except for some recent additions,
<algorithm> has been in the standard library from the very beginning. Yet if you look at code, people often prefer to handcraft their loops. Reasons range from ignorance of which algorithms are available to the belief that “templates are slow” (quite often without an explanation compared to what).
Things like template metaprogramming have been used in C++98. Compile-time logic can greatly reduce runtime complexity. Back in the day, it was not very convenient to use. Template syntax is quite different and much more involved than the features we got in the last standards. It’s more or less a second language we have to learn. However, things like tag dispatch and type traits are not too complex to use and write.
Yes, most type traits have been added to the standard library with C++11, but writing some for your own use cases is not that hard, and Boost had some general ones before C++11. I consider the use of compile-time logic as Modern C++ because it sets C++ apart from the omnipresent “C with classes”.
Modern C++ isn’t all about the new standards, it’s about the way we write our programs. Firstly, you can write a fairly modern C++98 style. Secondly, “C with classes and range based for” still isn’t modern C++. The language features and library additions help and enable us writing Modern C++, but they are not what makes our code Modern C++.