From C++98 to C++23

C++ · Lesson 30 of 32 6 min read Updated

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.

StandardMain additions
C++98First ISO standard: classes, templates, exceptions, namespaces, std::string, the STL containers and algorithms
C++03A bug-fix release with almost no new features
C++11auto, range-based for, lambdas, move semantics, std::unique_ptr and std::shared_ptr, nullptr, enum class, constexpr, override, threads, unordered_map
C++14std::make_unique, generic lambdas, return type deduction, digit separators like 1'000'000
C++17Structured bindings, if with initializer, std::optional, std::variant, std::string_view, std::filesystem, guaranteed copy elision, nested namespaces
C++20Concepts, ranges, modules, coroutines, <=>, std::format, std::span, std::jthread, designated initializers
C++23std::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++
// 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++
// 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
}
Output
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++
// 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;
C++
// 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
OldModern replacementWhy it is better
typedefusing aliasReads left to right; works with templates
NULL or 0nullptrHas its own type, no overload confusion
new / deletestd::make_unique, containersNo leaks or double deletes
std::auto_ptrstd::unique_ptrauto_ptr was removed in C++17
plain enumenum classScoped names, no implicit int conversion
#define constantsconstexprTyped and scoped
C-style castsstatic_cast and friendsVisible and checked
printf, std::cout chainsstd::printlnType-safe and readable

Maps and error handling: then and now

C++
// 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;
}
C++
// 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:

  1. Compile with a modern standard and warnings on: -std=c++23 -Wall -Wextra. Fix every warning.
  2. Replace NULL with nullptr and C-style casts with static_cast.
  3. Replace raw owning pointers with std::unique_ptr or containers; delete the manual delete calls.
  4. Replace iterator loops with range-based for or algorithms.
  5. Add override to every overriding virtual function and const to every member function that only reads.
  6. Turn plain enums into enum class, and #define constants into constexpr.
  7. Use std::string_view, std::optional and std::span in 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 added std::print, std::expected and import 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
C++
#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);
    }
}
Output
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++"?

2. What replaced std::auto_ptr?

3. In which standard did std::print and std::expected arrive?

4. Which statement is true about compiler defaults?

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