From C++98 to C++23
C++ has changed more since 2011 than in all the years before. Code written in the C++98 style still compiles today, and you will meet a lot of it in college syllabi, interviews and company codebases. But it looks very different from the modern C++ taught in this course, and it is much easier to get wrong.
This lesson puts the versions in order, shows old and new code side by side, and gives you a practical checklist for modernising older programs.
The standards at a glance
C++ is standardised by the ISO committee WG21. Since 2011 a new standard has appeared every three years.
| Standard | Main additions |
|---|---|
| C++98 | First ISO standard: classes, templates, exceptions, namespaces, std::string, the STL containers and algorithms |
| C++03 | A bug-fix release with almost no new features |
| C++11 | auto, range-based for, lambdas, move semantics, std::unique_ptr and std::shared_ptr, nullptr, enum class, constexpr, override, threads, unordered_map |
| C++14 | std::make_unique, generic lambdas, return type deduction, digit separators like 1'000'000 |
| C++17 | Structured bindings, if with initializer, std::optional, std::variant, std::string_view, std::filesystem, guaranteed copy elision, nested namespaces |
| C++20 | Concepts, ranges, modules, coroutines, <=>, std::format, std::span, std::jthread, designated initializers |
| C++23 | std::print and std::println, std::expected, import std;, std::ranges::to, enumerate and zip views, std::string::contains, std::to_underlying, deducing this |
C++11 is the big dividing line. People often call everything from C++11 onwards modern C++. C++23 was published by ISO in 2024 as ISO/IEC 14882:2024, and the committee finished its technical work on C++26 in March 2026; see C++26 Preview.
Compilers choose a default standard if you do not pass one: GCC 16 uses C++20 (GCC 11 to 15 used C++17), and MSVC uses C++14. Always state the standard you want with -std=c++23 or /std:c++latest.
Loops and containers: then and now
Here is the same task, adding up marks, in C++98 style and in C++23 style.
// C++98 style
#include <iostream>
#include <vector>
int main() {
std::vector<int> marks;
marks.push_back(78);
marks.push_back(92);
marks.push_back(65);
int total = 0;
for (std::vector<int>::const_iterator it = marks.begin(); it != marks.end(); ++it) {
total += *it;
}
std::cout << "Total: " << total << std::endl;
return 0;
}// C++23 style
#include <print>
#include <vector>
int main() {
std::vector marks = {78, 92, 65}; // C++11 list init + C++17 CTAD
int total = 0;
for (int m : marks) { // C++11 range-based for
total += m;
}
std::println("Total: {}", total); // C++23
}Total: 235
Both programs print the same line. The modern one initialises the vector in one statement, lets the compiler deduce std::vector<int>, and replaces the long iterator type with a range-based loop.
Memory, types and names
The biggest safety improvement is in memory management. Older code owns heap objects through raw pointers; modern code uses smart pointers and containers so that nothing has to be deleted by hand.
// C++98 style
typedef std::map<std::string, std::vector<int> > MarksTable; // space needed in "> >"
Student* s = new Student("Isha");
if (s->marks() < 0) {
delete s; // must remember delete on every path
return;
}
delete s;
Student* nobody = NULL;// Modern style
using MarksTable = std::map<std::string, std::vector<int>>; // C++11 alias, ">>" is fine
auto s = std::make_unique<Student>("Isha"); // C++14
if (s->marks() < 0) {
return; // deleted automatically
}
Student* nobody = nullptr; // C++11| Old | Modern replacement | Why it is better |
|---|---|---|
typedef | using alias | Reads left to right; works with templates |
NULL or 0 | nullptr | Has its own type, no overload confusion |
new / delete | std::make_unique, containers | No leaks or double deletes |
std::auto_ptr | std::unique_ptr | auto_ptr was removed in C++17 |
plain enum | enum class | Scoped names, no implicit int conversion |
#define constants | constexpr | Typed and scoped |
| C-style casts | static_cast and friends | Visible and checked |
printf, std::cout chains | std::println | Type-safe and readable |
Maps and error handling: then and now
// C++98 style: iterate a map and signal "not found" with -1
int find_marks(const std::map<std::string, int>& m, const std::string& name) {
std::map<std::string, int>::const_iterator it = m.find(name);
if (it == m.end()) {
return -1; // caller must remember that -1 means "missing"
}
return it->second;
}
for (std::map<std::string, int>::const_iterator it = m.begin(); it != m.end(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}// Modern style
std::optional<int> find_marks(const std::map<std::string, int>& m, const std::string& name) {
if (auto it = m.find(name); it != m.end()) { // C++17 if with initializer
return it->second;
}
return std::nullopt; // "missing" is part of the type
}
for (const auto& [student, score] : m) { // C++17 structured bindings
std::println("{}: {}", student, score);
}The modern version makes "missing" impossible to confuse with a real score, and the loop names the key and value instead of first and second.
A checklist for modernising old code
When you work on an older C++ codebase, improve it in small, safe steps and compile and test after each one:
- Compile with a modern standard and warnings on:
-std=c++23 -Wall -Wextra. Fix every warning. - Replace
NULLwithnullptrand C-style casts withstatic_cast. - Replace raw owning pointers with
std::unique_ptror containers; delete the manualdeletecalls. - Replace iterator loops with range-based
foror algorithms. - Add
overrideto every overriding virtual function andconstto every member function that only reads. - Turn plain enums into
enum class, and#defineconstants intoconstexpr. - Use
std::string_view,std::optionalandstd::spanin function parameters and return types where they make intent clearer.
Common mistakes
- Mixing styles randomly. In one codebase, follow one style; modernise whole functions or files at a time.
- Rewriting everything at once. Big rewrites introduce bugs. Change small pieces and test.
- Assuming every compiler supports the newest features. Check support tables before using C++23 library features in shared code.
- Learning only the old style for exams. You will need modern C++ in any real job.
Key points
- C++98 was the first standard; C++11 started modern C++; since then a new standard arrives every three years.
- C++14 and C++17 refined C++11; C++20 added concepts, ranges, modules and
std::format; C++23 addedstd::print,std::expectedandimport std;. - Modern code replaces raw
new/delete,NULL,typedef, plain enums and iterator loops with safer alternatives. - Compilers have different default standards, so always pass the one you want.
- Modernise old code in small, tested steps, starting with warnings.
Exercise
Rewrite this C++98 fragment in modern C++23 style: typedef std::vector<std::string> Names; Names* list = new Names(); list->push_back("Rohan"); list->push_back("Meera"); for (Names::iterator it = list->begin(); it != list->end(); ++it) { std::cout << *it << std::endl; } delete list;. Avoid new, use a using alias, a range-based loop and std::println.
Show solution
#include <print>
#include <string>
#include <vector>
using Names = std::vector<std::string>;
int main() {
Names list = {"Rohan", "Meera"};
for (const auto& name : list) {
std::println("{}", name);
}
}Rohan Meera
The vector does not need to live on the heap at all, so both new and delete disappear. The list initialisation replaces the push_back calls, and the range-based loop replaces the iterator loop.
Quick Quiz
1. Which standard is usually considered the start of "modern C++"?
C++11 added auto, lambdas, move semantics, smart pointers and much more.
2. What replaced std::auto_ptr?
auto_ptr was deprecated in C++11 and removed in C++17; unique_ptr is its safe replacement.
3. In which standard did std::print and std::expected arrive?
Both are C++23 library additions.
4. Which statement is true about compiler defaults?
Default standards differ between compilers and versions, so pass -std=c++23 or /std:c++latest.
Learn C++ with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
