Wrap · #13 of 13

What to Skip (and Why)

The parts of C++ this course intentionally de-emphasized — and what to reach for instead

Why this lesson exists

The C++ standard is a 1,800-page document describing forty years of accumulated decisions. A complete C++ course would teach all of it. A useful C++ course teaches the ~30% you’ll actually write in 2026 and points at the rest with “read about this if it bites you.”

The previous twelve lessons taught the 30%. This lesson is the opinionated list of what’s left, with brief notes on why each topic was de-emphasized and where it still matters.

Deep inheritance hierarchies

For most of C++‘s history, the recommended “OOP design” looked like:

class Shape {
 public:
  virtual double area() const = 0;
  virtual void   draw() const = 0;
  virtual ~Shape() = default;
};

class Circle    : public Shape { /* … */ };
class Rectangle : public Shape { /* … */ };
class Square    : public Rectangle { /* … */ };

Modern C++ generally avoids this. Value types, free functions, templates, and std::variant cover the use cases inheritance was solving, usually better:

The exception: framework code that requires you to inherit (Qt, some game engines, unit-test frameworks). Use it the framework’s way; don’t fight it.

Exception-based control flow

C++ has exceptions. The standard library throws (std::bad_alloc, std::out_of_range, std::system_error). Many codebases catch and rethrow as part of normal control flow.

This course de-emphasized exceptions because:

When exceptions are the right tool:

For everything else, prefer std::expected or std::optional or a plain status enum.

iostream ceremony

std::cout << x is fine. std::getline(std::cin, line) is fine. Everything past that — manipulators, formatters via << chains, boost::format, the locale system — is mostly legacy.

C++20 added std::format (a Python f-string-shaped formatter) and C++23 added std::print:

#include <print>

std::print("user {} has {} points\n", name, score);

That’s the modern spelling. It’s faster than iostream, type-safe, locale-aware on demand, and reads like every other modern formatting library.

iostream is still needed for object overloads of operator<< (the canonical “make my type printable” extension point) and for files via std::fstream. The everyday formatting use case is now std::format / std::print.

new and delete directly

Lesson 05’s point, restated: in modern C++ application code, raw new and delete should be rare. Reach for std::make_unique, std::make_shared, containers, or stack allocation. The remaining legitimate uses:

If a code review presents you with a new without a smart-pointer wrapper, treat it as a defect until proven otherwise.

C-style arrays (mostly)

int arr[10]; exists. It works. You almost never need it. std::array<int, 10> has the same size and layout, plus .size(), .begin()/.end(), range-for, and won’t decay to a pointer on the slightest excuse. std::vector<int> is the answer when the size is dynamic.

The exception: C interop. When a C function takes int* arr, size_t n, pass vec.data(), vec.size(). You don’t need a C-style array literally.

unsigned for arithmetic

C++ inherits C’s behavior that mixing signed and unsigned ints in a comparison silently converts the signed one. This produces bugs like:

int  i = -1;
unsigned u = 1;
if (i < u) { /* never enters — i became 2^32 - 1 */ }

Modern style: use signed integers for arithmetic. Reserve unsigned for bit-flags and opaque IDs. std::size_t is unsigned by historical accident; the Bjarne / Sutter / Stroustrup advice is to use int or std::ptrdiff_t for sizes when you can, and pay the cast tax when interfacing with the standard library.

This is contentious. Code that lives entirely inside the standard library’s unsigned-size conventions can stay that way; mixed code does benefit from signed sizes. Read your local style guide.

auto everywhere vs. spelled types

Some codebases lean into auto; others ban it outside iterator declarations. Both work.

The middle path most readable for the next person:

Old-style casts

double d = 3.14;
int i = (int)d;          // C-style cast — works but unsafe
int j = static_cast<int>(d);    // C++ form — only does what you asked

C-style casts can be a static_cast, const_cast, reinterpret_cast, or a combination. They’re the cast that fails silently. Always reach for the named C++ casts:

C-style casts in your own code are a yellow flag. Old code may have plenty; modernize where you can.

Threads where async I/O fits

The standard library’s threading is for CPU-bound parallelism. For I/O-bound work — talking to lots of sockets, files, databases — threads scale poorly. Each blocked thread is a stack and a context switch.

C++ doesn’t ship a standard async I/O framework yet. The ecosystem options:

Until C++26 ships executors, picking an async story is a per-project decision. Don’t bolt threads onto an I/O problem.

RTTI and typeid

C++ has runtime type information. typeid(x).name() gives you a mangled string for x’s type. dynamic_cast uses RTTI to do runtime-checked downcasts.

The cost: every polymorphic class carries an extra pointer (the vtable’s type info), and dynamic_cast does string comparison or hash lookup at runtime. Embedded codebases routinely compile with -fno-rtti.

Use sparingly. Most “I want to know the type” situations are better solved with std::variant (closed alternatives) or a discriminator field (open alternatives).

goto

Yes, C++ has goto. No, do not use it. The one historically defensible use — “break out of nested loops” — has lambdas + return as a cleaner replacement, or you can refactor into a function.

What’s left

A short list of topics this course did not cover that you may need eventually:

Key takeaways

What’s next

You’ve reached the end of the linear curriculum. The capstones — C1 through C4 — are where the lessons fuse into projects:

Pick the one that sounds most fun. C2 is the most “data structures”; C3 is the most “systems”; C4 is the most “everything you’ve learned.”