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 safeType 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 arrayModern 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; });