Modern C++ Idioms

Essential patterns for effective C++ programming

RAII (Resource Acquisition Is Initialization)

Tie resource lifetime to object lifetime for automatic cleanup.

// Bad: Manual resource management
void bad() {
    auto* ptr = new int[100];
    // What if exception thrown here? Memory leak!
    delete[] ptr;
}

// Good: RAII with smart pointer
void good() {
    auto ptr = std::make_unique<int[]>(100);
    // Automatically freed when ptr goes out of scope
}

// Better: Use container
void better() {
    std::vector<int> vec(100);
    // Automatically freed, plus bounds checking
}

Rule of 0/3/5

// Rule of 0: Let the compiler handle it
class Good {
    std::string name_;
    std::vector<int> data_;
    std::unique_ptr<Resource> resource_;
    // Compiler-generated copy/move/destructor are correct!
};

// Rule of 5: Define all or explicitly default/delete
class Manual {
    char* buffer_;
    size_t size_;
public:
    // Constructor
    explicit Manual(size_t size) : buffer_(new char[size]), size_(size) {}
    
    // Destructor
    ~Manual() { delete[] buffer_; }
    
    // Copy constructor
    Manual(const Manual& other) : buffer_(new char[other.size_]), size_(other.size_) {
        std::copy(other.buffer_, other.buffer_ + size_, buffer_);
    }
    
    // Copy assignment
    Manual& operator=(const Manual& other) { /* ... */ return *this; }
    
    // Move constructor
    Manual(Manual&& other) noexcept : buffer_(other.buffer_), size_(other.size_) {
        other.buffer_ = nullptr;
        other.size_ = 0;
    }
    
    // Move assignment
    Manual& operator=(Manual&& other) noexcept { /* ... */ return *this; }
};

Copy-and-Swap Idiom

class Array {
    std::unique_ptr<int[]> data_;
    size_t size_;
    
public:
    // Copy constructor
    Array(const Array& other) 
        : data_(std::make_unique<int[]>(other.size_))
        , size_(other.size_) {
        std::copy(other.data_.get(), other.data_.get() + size_, data_.get());
    }
    
    // Move constructor (cheap!)
    Array(Array&&) noexcept = default;
    
    // Swap (non-throwing)
    friend void swap(Array& a, Array& b) noexcept {
        using std::swap;
        swap(a.data_, b.data_);
        swap(a.size_, b.size_);
    }
    
    // Assignment operator - copy-and-swap
    Array& operator=(Array other) {  // Pass by value (copy)
        swap(*this, other);          // No-throw swap
        return *this;                // Other destroyed with old state
    }
};

// Benefits:
// - Copy and assignment share code
// - Strong exception guarantee
// - Self-assignment safe

Type Safety Idioms

// Strong types - prevent mixing up similar types
struct Kilometers { double value; };
struct Miles { double value; };

void travel(Kilometers distance);  // Can't accidentally pass Miles

// Scoped enums - type safe
enum class Color { Red, Green, Blue };
Color c = Color::Red;  // Must qualify name

// Not implicitly convertible to int
// int x = Color::Red;  // Error!

// Optional for nullable values
std::optional<User> find_user(int id);

// Expected for error handling
std::expected<Data, Error> load_data();

// Span for non-owning array reference
void process(std::span<const int> data);  // Works with vector, array, C array

Modern Loop Patterns

std::vector<int> numbers = {1, 2, 3, 4, 5};

// Range-based for loop (C++11)
for (int n : numbers) {
    std::cout << n << std::endl;
}

// With auto and reference
for (const auto& item : container) {
    // Read-only access, no copy
}

// Structured bindings (C++17)
std::map<std::string, int> scores;
for (const auto& [name, score] : scores) {
    std::cout << name << ": " << score << std::endl;
}

// STL algorithms instead of loops
std::for_each(numbers.begin(), numbers.end(), [](int n) {
    std::cout << n << std::endl;
});

// Ranges (C++20)
auto evens = numbers | std::views::filter([](int n) { return n % 2 == 0; });