Skip to main content
Smart Pointers

Modern C++ (C++11 to C++20)

“Modern C++” refers to the massive evolution of the language starting with C++11. It emphasizes safety, expressiveness, and performance. If you are writing C++ like it’s 1998, you are working too hard.

1. Type Inference (auto)

Let the compiler deduce types for you. It prevents typos and makes refactoring easier.
// Before: Verbose and hard to read
std::vector<std::string>::iterator it = names.begin();

// After: Clean and clear
auto it = names.begin();

2. Smart Pointers (Memory Safety)

We covered this in the Memory chapter, but it bears repeating: Never use new and delete.
  • std::unique_ptr: The default choice. Fast, safe, exclusive ownership.
  • std::shared_ptr: Use only when ownership is truly shared.
auto ptr = std::make_unique<MyClass>();
// Automatically deleted when ptr goes out of scope. No leaks!

3. Move Semantics (Performance)

Before C++11, returning large objects (like a vector with 1M items) involved copying the entire object. This was slow. Move Semantics allow resources to be “stolen” from temporary objects. Instead of copying the data, we just copy the pointer to the data. It’s like handing over the keys to a house instead of building a replica house.
std::vector<int> createVector() {
    std::vector<int> v(1000);
    return v; // Moves v out (cheap), doesn't copy (expensive)
}

4. Structured Bindings (C++17)

Allows you to unpack tuples, pairs, and structs directly into variables. It’s similar to destructuring in JavaScript/Python.
std::map<std::string, int> scores = {{"Alice", 10}, {"Bob", 20}};

// Unpack Key and Value directly
for (const auto& [name, score] : scores) {
    std::cout << name << ": " << score << "\n";
}

5. std::optional (C++17)

Express that a value might be missing without using “magic numbers” (like returning -1 for error) or null pointers. It forces you to handle the “empty” case.
#include <optional>

std::optional<std::string> findUser(int id) {
    if (id == 1) return "Alice";
    return std::nullopt; // Explicitly return "nothing"
}

auto user = findUser(2);
if (user.has_value()) {
    std::cout << user.value();
} else {
    std::cout << "User not found";
}

6. std::variant (C++17)

A type-safe union. It can hold one of several types. Useful for state machines or parsing.
#include <variant>

std::variant<int, std::string> data;

data = 10;
std::cout << std::get<int>(data); // 10

data = "Hello";
std::cout << std::get<std::string>(data); // Hello

7. Concepts (C++20)

Concepts allow you to constrain template parameters. Instead of getting a 100-line error message when you pass the wrong type to a template, you get a clear, readable error.
#include <concepts>

// Only accept types that are integral (int, long, etc.)
template <std::integral T>
T add(T a, T b) {
    return a + b;
}

add(10, 20);    // OK
add(1.5, 2.5);  // Compile Error: double is not integral. Clear and concise.

8. Ranges (C++20)

Ranges allow you to compose algorithms using the pipe operator (|). It makes code read like a data processing pipeline.
#include <ranges>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6};

    // Pipeline: Filter evens -> Square them -> Take first 2
    auto result = nums 
        | std::views::filter([](int n) { return n % 2 == 0; })
        | std::views::transform([](int n) { return n * n; })
        | std::views::take(2);

    for (int n : result) {
        std::cout << n << " "; // 4 16
    }
}

Summary

Modern C++ is a different beast from “C with Classes”.
  • Safety: Smart pointers and std::optional eliminate common bugs.
  • Expressiveness: auto, structured bindings, and ranges make code readable.
  • Performance: Move semantics ensure abstractions are zero-cost.
You are now equipped with the knowledge to write professional, modern C++. Happy coding!