cpp-know-hows

cpp related stuff

View on GitHub

C++ Best Practices and Idioms

Overview

This chapter consolidates best practices, common idioms, and guidelines for writing modern, efficient, and maintainable C++ code.

┌──────────────────────────────────────────────────────────┐
│                  BEST PRACTICES AREAS                    │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  CODE QUALITY      │  PERFORMANCE     │  SAFETY          │
│  ─────────────     │  ────────────    │  ──────          │
│  • Readability     │  • Optimization  │  • Type safety   │
│  • Maintainability │  • Profiling     │  • Bounds checks │
│  • Documentation   │  • Benchmarking  │  • Exception safe│
│                                                          │
│  MODERN C++        │  DESIGN          │  TESTING         │
│  ──────────        │  ──────          │  ───────         │
│  • auto, range-for │  • SOLID         │  • Unit tests    │
│  • Smart pointers  │  • Patterns      │  • TDD           │
│  • Algorithms      │  • Modularity    │  • Coverage      │
│                                                          │
└──────────────────────────────────────────────────────────┘

Core Guidelines Highlights

Use RAII for Resource Management

// BAD: Manual resource management
void process() {
    int* data = new int[1000];
    // ... processing ...
    delete[] data;  // Easy to forget, exception unsafe
}

// GOOD: RAII with smart pointers or containers
void process() {
    std::vector<int> data(1000);
    // ... processing ...
}  // Automatically cleaned up

Prefer auto for Type Deduction

// BAD: Redundant type information
std::vector<int>::iterator it = vec.begin();
std::map<std::string, std::vector<int>>::const_iterator mit = map.cbegin();

// GOOD: Let compiler deduce types
auto it = vec.begin();
auto mit = map.cbegin();

// EXCEPTION: When explicit type aids readability
int count = calculate();  // Clear that it's an int
auto value = calculate(); // What type is this?

Use Range-based for Loops

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

// BAD: Manual iteration
for (size_t i = 0; i < vec.size(); ++i) {
    process(vec[i]);
}

// GOOD: Range-based for
for (int value : vec) {
    process(value);
}

// BEST: const ref to avoid copies
for (const auto& value : vec) {
    process(value);
}

Prefer emplace over insert/push_back

std::vector<std::string> vec;

// BAD: Creates temporary then copies/moves
vec.push_back(std::string("Hello"));

// GOOD: Constructs in-place
vec.emplace_back("Hello");

// For complex types:
struct Point { int x, y; Point(int x, int y) : x(x), y(y) {} };
std::vector<Point> points;

points.push_back(Point(1, 2));  // BAD: Creates temporary
points.emplace_back(1, 2);      // GOOD: Constructs in-place

Performance Best Practices

Pass by const Reference

// BAD: Copies for large objects
void process(std::string s, std::vector<int> v) {
    // ...
}

// GOOD: Pass by const reference
void process(const std::string& s, const std::vector<int>& v) {
    // ...
}

// For small types, pass by value is OK
void process(int x, double y) {  // OK: cheap to copy
    // ...
}

Return by Value (Enable RVO)

// BAD: Output parameter
void make_vector(std::vector<int>& out) {
    out = {1, 2, 3, 4, 5};
}

// GOOD: Return by value (RVO)
std::vector<int> make_vector() {
    return {1, 2, 3, 4, 5};  // No copy with RVO
}

Reserve Capacity for Containers

std::vector<int> vec;

// BAD: Multiple reallocations
for (int i = 0; i < 10000; ++i) {
    vec.push_back(i);  // May reallocate many times
}

// GOOD: Reserve first
std::vector<int> vec;
vec.reserve(10000);
for (int i = 0; i < 10000; ++i) {
    vec.push_back(i);  // No reallocations
}

Use Appropriate Containers

┌────────────────────────────────────────────────────────┐
│              Container Selection Guide                 │
├────────────────────────────────────────────────────────┤
│ Need                  │ Use                            │
├───────────────────────┼────────────────────────────────┤
│ Random access         │ std::vector, std::array        │
│ Insertion at ends     │ std::deque                     │
│ Frequent middle ins.  │ std::list                      │
│ Unique sorted         │ std::set                       │
│ Key-value sorted      │ std::map                       │
│ Unique unordered      │ std::unordered_set             │
│ Key-value unordered   │ std::unordered_map             │
│ LIFO                  │ std::stack                     │
│ FIFO                  │ std::queue                     │
│ Priority              │ std::priority_queue            │
└────────────────────────────────────────────────────────┘

Prefer Algorithms over Loops

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

// BAD: Manual loop
int sum = 0;
for (int x : vec) {
    sum += x;
}

// GOOD: Use algorithm
int sum = std::accumulate(vec.begin(), vec.end(), 0);

// BEST: Ranges (C++20)
int sum = std::ranges::fold_left(vec, 0, std::plus{});

Modern C++ Idioms

Almost Always Auto (AAA)

// Use auto to avoid repetition and errors
auto value = calculate_something();
auto it = container.find(key);
auto result = expensive_function();

// Especially with lambdas
auto lambda = [](int x) { return x * 2; };

// For iterators
for (auto it = vec.begin(); it != vec.end(); ++it) {
    // ...
}

Immediately Invoked Lambda Expression (IILE)

// Initialize const variables with complex logic
const auto value = [&]() {
    if (condition1) return 42;
    if (condition2) return 100;
    return 0;
}();

// One-time initialization
const auto config = []() {
    Config c;
    c.load("config.txt");
    c.validate();
    return c;
}();

Copy-and-Swap Idiom

class Resource {
    int* data_;
    
public:
    // Copy constructor
    Resource(const Resource& other) 
        : data_(new int(*other.data_)) {}
    
    // Copy assignment using copy-and-swap
    Resource& operator=(Resource other) {  // Pass by value
        swap(other);  // Swap with temporary
        return *this;
    }  // Temporary destroyed, cleaning up old data
    
    void swap(Resource& other) noexcept {
        std::swap(data_, other.data_);
    }
};

RAII Wrapper Pattern

// Wrap C resources with RAII
class FileHandle {
    FILE* file_;
public:
    explicit FileHandle(const char* filename, const char* mode)
        : file_(fopen(filename, mode)) {
        if (!file_) throw std::runtime_error("Failed to open file");
    }
    
    ~FileHandle() { if (file_) fclose(file_); }
    
    // No copy
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
    
    // Allow move
    FileHandle(FileHandle&& other) noexcept 
        : file_(other.file_) {
        other.file_ = nullptr;
    }
    
    FILE* get() const { return file_; }
};

Pimpl (Pointer to Implementation)

// Header: widget.h
class Widget {
public:
    Widget();
    ~Widget();
    void do_something();
    
private:
    class Impl;  // Forward declaration
    std::unique_ptr<Impl> pimpl_;
};

// Implementation: widget.cpp
class Widget::Impl {
public:
    void do_something() { /* implementation */ }
private:
    // Private members hidden from header
    int data_;
    std::vector<int> more_data_;
};

Widget::Widget() : pimpl_(std::make_unique<Impl>()) {}
Widget::~Widget() = default;  // Must be in cpp (Impl is complete here)
void Widget::do_something() { pimpl_->do_something(); }

Safety Best Practices

Avoid Raw Pointers for Ownership

// BAD: Who owns this? When to delete?
int* create_data() {
    return new int(42);
}

// GOOD: Clear ownership
std::unique_ptr<int> create_data() {
    return std::make_unique<int>(42);
}

Use const Liberally

// Mark what doesn't change
void process(const std::string& input) {  // Won't modify input
    // ...
}

// Const member functions
class Calculator {
    int value_;
public:
    int get_value() const { return value_; }  // Won't modify object
    void set_value(int v) { value_ = v; }     // Can modify
};

// Const correctness enables optimization

Initialize Variables

// BAD: Uninitialized
int x;
std::string s;  // OK: default constructed
int* ptr;       // BAD: dangling

// GOOD: Initialize
int x = 0;
int y{42};  // Uniform initialization
std::string s = "Hello";
int* ptr = nullptr;

// C++17: Structured bindings
if (auto [iter, inserted] = map.insert({key, value}); inserted) {
    // Use iter
}

Check Return Values

// BAD: Ignore errors
file.open("data.txt");
file.write(data);

// GOOD: Check for errors
if (!file.open("data.txt")) {
    std::cerr << "Failed to open file\n";
    return;
}

// BETTER: Use exceptions for errors
try {
    File file("data.txt");
    file.write(data);
} catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << '\n';
}

Code Organization

Header-Only Libraries

// my_utility.hpp
#ifndef MY_UTILITY_HPP
#define MY_UTILITY_HPP

namespace my_utility {

// Inline for header-only
inline int add(int a, int b) {
    return a + b;
}

// Template (naturally header-only)
template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// C++17: Inline variables
inline constexpr int version = 1;

}  // namespace my_utility

#endif

Namespaces

// Avoid `using namespace std` in headers
// BAD (in header):
using namespace std;

// GOOD (in cpp):
namespace {  // Anonymous namespace for file-local
    constexpr int BUFFER_SIZE = 1024;
    void helper_function() { /* ... */ }
}

namespace mylib {
    void public_function() { /* ... */ }
}

// Avoid deeply nested namespaces
namespace mycompany::myproject::mymodule {  // C++17
    // ...
}

Forward Declarations

// header.hpp
class BigClass;  // Forward declare

class MyClass {
    std::unique_ptr<BigClass> member_;  // OK: pointer to incomplete type
public:
    void process(const BigClass& obj);  // OK: reference
};

// cpp file includes full definition
#include "big_class.hpp"

Error Handling

Prefer Exceptions for Errors

// BAD: Error codes
int open_file(const char* name, File& out) {
    if (/* error */) return -1;
    // ...
    return 0;
}

// GOOD: Exceptions
File open_file(const char* name) {
    if (/* error */) {
        throw std::runtime_error("Failed to open file");
    }
    // ...
    return file;
}

Exception Safety Guarantees

┌────────────────────────────────────────────────────────┐
│            Exception Safety Levels                     │
├────────────────────────────────────────────────────────┤
│                                                        │
│ 1. No-throw (noexcept)                                │
│    - Never throws exceptions                           │
│    - Destructors, move operations, swap                │
│                                                        │
│ 2. Strong guarantee                                    │
│    - Either succeeds or leaves unchanged               │
│    - Commit-or-rollback semantics                      │
│                                                        │
│ 3. Basic guarantee                                     │
│    - No resources leaked                               │
│    - Object remains in valid state                     │
│                                                        │
│ 4. No guarantee                                        │
│    - May leak resources or leave inconsistent          │
│    - Avoid this!                                       │
│                                                        │
└────────────────────────────────────────────────────────┘
// Strong exception guarantee using copy-and-swap
void Widget::set_data(const Data& new_data) {
    Data temp = new_data;  // May throw
    data_.swap(temp);      // noexcept
    // If exception thrown, Widget unchanged
}

// Mark noexcept when appropriate
void important_operation() noexcept {
    // Must not throw
}

Testing Best Practices

Unit Testing Example

// Using a testing framework (e.g., Catch2, Google Test)
#include <catch2/catch.hpp>

TEST_CASE("Vector operations", "[vector]") {
    std::vector<int> vec;
    
    SECTION("push_back adds elements") {
        vec.push_back(1);
        vec.push_back(2);
        
        REQUIRE(vec.size() == 2);
        REQUIRE(vec[0] == 1);
        REQUIRE(vec[1] == 2);
    }
    
    SECTION("clear empties vector") {
        vec.push_back(1);
        vec.clear();
        
        REQUIRE(vec.empty());
    }
}

Assertions and Contracts

#include <cassert>

void process(int* data, size_t size) {
    assert(data != nullptr);  // Precondition
    assert(size > 0);         // Precondition
    
    // ... processing ...
    
    assert(is_valid_result());  // Postcondition
}

// C++20: Contracts (planned, not yet standardized)
// void process(int* data, size_t size)
//     [[expects: data != nullptr]]
//     [[expects: size > 0]]
//     [[ensures: result >= 0]]
// { ... }

Documentation

Comments Best Practices

// BAD: States the obvious
int count = 0;  // Set count to 0

// GOOD: Explains why
int count = 0;  // Track number of retries for exponential backoff

// BAD: Outdated comment
int limit = 1000;  // Maximum of 100 connections

// GOOD: Self-documenting code
constexpr int MAX_CONNECTIONS = 1000;

// Document non-obvious behavior
/// Returns the index of the element, or -1 if not found.
/// Time complexity: O(log n) for sorted containers.
/// Thread-safe: No
int find_index(const std::vector<int>& vec, int target);

Doxygen-style Documentation

/**
 * @brief Calculates the distance between two points.
 * 
 * @param p1 The first point
 * @param p2 The second point
 * @return The Euclidean distance between p1 and p2
 * 
 * @note This function uses floating-point arithmetic.
 * @warning Large coordinate values may cause overflow.
 */
double distance(const Point& p1, const Point& p2);

Compilation and Build

Compiler Warnings

# Enable all warnings and treat as errors
g++ -Wall -Wextra -Werror -pedantic -std=c++20 main.cpp

# Additional useful warnings
g++ -Wshadow -Wconversion -Wsign-conversion -Wnon-virtual-dtor

Optimization Flags

# Debug build
g++ -g -O0 -DDEBUG main.cpp

# Release build
g++ -O3 -DNDEBUG -march=native main.cpp

# Profiling build
g++ -pg -O2 main.cpp

Common Anti-Patterns to Avoid

1. God Class

// BAD: Class does everything
class System {
    void process_input();
    void render_graphics();
    void handle_network();
    void manage_audio();
    void save_file();
    // ... 100 more methods
};

// GOOD: Separate responsibilities
class InputHandler { /*...*/ };
class Renderer { /*...*/ };
class NetworkManager { /*...*/ };

2. Premature Optimization

// BAD: Optimizing before profiling
void process(const std::vector<int>& data) {
    // Complex bit-twiddling optimization
    // that's hard to read and maintain
}

// GOOD: Write clear code first
void process(const std::vector<int>& data) {
    // Clear, simple implementation
    // Optimize later if profiling shows bottleneck
}

3. Naked new and delete

// BAD: Manual memory management
int* data = new int[size];
// ... use data ...
delete[] data;

// GOOD: Use containers or smart pointers
std::vector<int> data(size);
auto data2 = std::make_unique<int[]>(size);

4. Ignoring const-correctness

// BAD: Non-const getters
class Point {
    int x_, y_;
public:
    int x() { return x_; }  // Can't be called on const Point
    int y() { return y_; }
};

// GOOD: Const-correct getters
class Point {
    int x_, y_;
public:
    int x() const { return x_; }
    int y() const { return y_; }
};

Performance Measurement

Basic Benchmarking

#include <chrono>

template<typename Func>
auto measure_time(Func func) {
    auto start = std::chrono::high_resolution_clock::now();
    func();
    auto end = std::chrono::high_resolution_clock::now();
    return std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
}

int main() {
    auto duration = measure_time([]() {
        // Code to benchmark
        std::vector<int> vec(1000000);
        std::sort(vec.begin(), vec.end());
    });
    
    std::cout << "Time: " << duration.count() << "ms\n";
    
    return 0;
}

Quick Reference Checklist

Before Committing Code

✓ Code compiles with -Wall -Wextra -Werror
✓ No memory leaks (run valgrind)
✓ All tests pass
✓ Code is formatted consistently
✓ Complex logic is commented
✓ Public API is documented
✓ No compiler warnings
✓ Performance is acceptable (if relevant)
✓ Exception safety considered
✓ const-correctness verified

Code Review Checklist

✓ Correct algorithm/data structure choice
✓ Resource management (RAII)
✓ Error handling appropriate
✓ Thread safety (if concurrent)
✓ No unnecessary copies
✓ Const-correctness
✓ Clear variable/function names
✓ Appropriate use of modern C++ features
✓ Tests cover edge cases
✓ Documentation clear and accurate

Static Analysis

Dynamic Analysis

Profiling

Build Systems


Further Learning

Essential Reading

Online Resources


Conclusion

Modern C++ is a powerful language that rewards careful design and attention to best practices. Key takeaways:

  1. Use RAII for all resource management
  2. Prefer value semantics and smart pointers
  3. Embrace modern C++ features (auto, range-for, lambdas)
  4. Write clear code first, optimize later
  5. Use STL algorithms instead of raw loops
  6. Test thoroughly and use static analysis
  7. Document your code for maintainability
  8. Follow the Core Guidelines

Remember: Correct, clear, and maintainable code is more valuable than clever code!


Complete Practical Example: Modern C++ Application

Here’s a comprehensive example applying all best practices - a complete, production-quality application:

#include <iostream>
#include <string>
#include <string_view>
#include <vector>
#include <optional>
#include <variant>
#include <memory>
#include <algorithm>
#include <ranges>
#include <chrono>
#include <stdexcept>
#include <cassert>

// 1. Strong types for type safety
class UserId {
    int value_;
public:
    explicit UserId(int value) : value_(value) {
        if (value < 0) {
            throw std::invalid_argument("UserId must be non-negative");
        }
    }
    int value() const noexcept { return value_; }
    auto operator<=>(const UserId&) const = default;
};

class Email {
    std::string value_;
public:
    explicit Email(std::string email) : value_(std::move(email)) {
        if (!is_valid(value_)) {
            throw std::invalid_argument("Invalid email format");
        }
    }
    const std::string& value() const noexcept { return value_; }
    
private:
    static bool is_valid(std::string_view email) {
        return email.find('@') != std::string_view::npos;
    }
};

// 2. Well-designed entity class (Rule of Zero)
class User {
private:
    UserId id_;
    std::string name_;
    Email email_;
    std::chrono::system_clock::time_point created_at_;
    
public:
    User(UserId id, std::string name, Email email)
        : id_(id)
        , name_(std::move(name))
        , email_(std::move(email))
        , created_at_(std::chrono::system_clock::now()) {
        
        if (name_.empty()) {
            throw std::invalid_argument("Name cannot be empty");
        }
    }
    
    // Getters (const-correct, noexcept where possible)
    UserId id() const noexcept { return id_; }
    const std::string& name() const noexcept { return name_; }
    const Email& email() const noexcept { return email_; }
    auto created_at() const noexcept { return created_at_; }
    
    // Business logic
    bool is_recently_created(std::chrono::hours threshold = std::chrono::hours{24}) const {
        auto now = std::chrono::system_clock::now();
        return now - created_at_ < threshold;
    }
};

// 3. Error handling with optional and variant
using DatabaseError = std::string;
template<typename T>
using Result = std::variant<T, DatabaseError>;

// 4. Repository pattern with clear ownership
class UserRepository {
private:
    std::vector<std::unique_ptr<User>> users_;
    
public:
    // Add user (transfer ownership)
    UserId add_user(std::unique_ptr<User> user) {
        if (!user) {
            throw std::invalid_argument("Cannot add null user");
        }
        
        auto id = user->id();
        users_.push_back(std::move(user));
        return id;
    }
    
    // Find user (return non-owning pointer)
    const User* find_by_id(UserId id) const noexcept {
        auto it = std::ranges::find_if(users_, [id](const auto& user) {
            return user->id() == id;
        });
        
        return it != users_.end() ? it->get() : nullptr;
    }
    
    // Find users by predicate (return view, not copies)
    auto find_if(auto predicate) const {
        return users_ 
            | std::views::transform([](const auto& ptr) -> const User& { 
                return *ptr; 
              })
            | std::views::filter(predicate);
    }
    
    // Get all recently created users
    auto get_recent_users() const {
        return find_if([](const User& user) {
            return user.is_recently_created();
        });
    }
    
    size_t size() const noexcept { return users_.size(); }
    
    // Clear users
    void clear() noexcept {
        users_.clear();
    }
};

// 5. Service layer with dependency injection
class UserService {
private:
    UserRepository& repository_;
    
public:
    explicit UserService(UserRepository& repository) 
        : repository_(repository) {}
    
    // Create user with validation
    Result<UserId> create_user(int id, std::string name, std::string email) noexcept {
        try {
            auto user = std::make_unique<User>(
                UserId(id),
                std::move(name),
                Email(std::move(email))
            );
            
            auto user_id = repository_.add_user(std::move(user));
            return user_id;
            
        } catch (const std::exception& e) {
            return DatabaseError{e.what()};
        }
    }
    
    // Get user details
    std::optional<std::string> get_user_info(UserId id) const noexcept {
        if (const User* user = repository_.find_by_id(id)) {
            return std::string("User: ") + user->name() + 
                   " <" + user->email().value() + ">";
        }
        return std::nullopt;
    }
    
    // Get recent user count
    size_t get_recent_user_count() const noexcept {
        return std::ranges::distance(repository_.get_recent_users());
    }
    
    // List all users (string_view parameter, no copying)
    void list_users(std::string_view prefix = "") const {
        for (const auto& [user] : std::views::enumerate(
            repository_.find_if([](const User&) { return true; }))) {
            std::cout << prefix << "User " << user.name() 
                      << " (" << user.email().value() << ")\n";
        }
    }
};

// 6. RAII timer for performance measurement
class ScopedTimer {
private:
    std::string_view name_;
    std::chrono::high_resolution_clock::time_point start_;
    
public:
    explicit ScopedTimer(std::string_view name)
        : name_(name)
        , start_(std::chrono::high_resolution_clock::now()) {
        std::cout << "[" << name_ << "] Started\n";
    }
    
    ~ScopedTimer() {
        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
            end - start_
        );
        std::cout << "[" << name_ << "] Completed in " 
                  << duration.count() << " μs\n";
    }
    
    // Non-copyable, non-movable
    ScopedTimer(const ScopedTimer&) = delete;
    ScopedTimer& operator=(const ScopedTimer&) = delete;
};

// 7. Configuration with modern C++ features
struct AppConfig {
    static constexpr int DEFAULT_MAX_USERS = 1000;
    static constexpr std::string_view APP_NAME = "UserManager";
    
    int max_users = DEFAULT_MAX_USERS;
    bool debug_mode = false;
    
    // Validation
    [[nodiscard]] bool is_valid() const noexcept {
        return max_users > 0 && max_users <= 100000;
    }
};

// 8. Application class using all best practices
class Application {
private:
    AppConfig config_;
    UserRepository repository_;
    UserService service_;
    
public:
    explicit Application(AppConfig config = {})
        : config_(std::move(config))
        , repository_()
        , service_(repository_) {
        
        if (!config_.is_valid()) {
            throw std::invalid_argument("Invalid configuration");
        }
        
        std::cout << "=== " << config_.APP_NAME << " Started ===\n";
        if (config_.debug_mode) {
            std::cout << "DEBUG MODE ENABLED\n";
        }
    }
    
    void run() {
        ScopedTimer timer("Application Run");
        
        // Create sample users
        create_sample_users();
        
        // Demonstrate queries
        demonstrate_queries();
        
        // Show statistics
        show_statistics();
    }
    
private:
    void create_sample_users() {
        ScopedTimer timer("Create Users");
        
        struct UserData { int id; const char* name; const char* email; };
        constexpr UserData users[] = {
            {1, "Alice Johnson", "alice@example.com"},
            {2, "Bob Smith", "bob@example.com"},
            {3, "Charlie Brown", "charlie@example.com"},
            {4, "Diana Prince", "diana@example.com"},
            {5, "Eve Wilson", "eve@example.com"}
        };
        
        for (const auto& [id, name, email] : users) {
            auto result = service_.create_user(id, name, email);
            
            std::visit([&](auto&& value) {
                using T = std::decay_t<decltype(value)>;
                if constexpr (std::is_same_v<T, UserId>) {
                    if (config_.debug_mode) {
                        std::cout << "  Created user: " << name << "\n";
                    }
                } else {
                    std::cerr << "  Failed: " << value << "\n";
                }
            }, result);
        }
        
        std::cout << "Created " << repository_.size() << " users\n";
    }
    
    void demonstrate_queries() {
        std::cout << "\n--- User Queries ---\n";
        
        // Find specific user
        if (auto info = service_.get_user_info(UserId{2})) {
            std::cout << *info << "\n";
        }
        
        // Find all users (using ranges)
        std::cout << "\nAll users:\n";
        service_.list_users("  ");
    }
    
    void show_statistics() {
        std::cout << "\n--- Statistics ---\n";
        std::cout << "Total users: " << repository_.size() << "\n";
        std::cout << "Recent users: " << service_.get_recent_user_count() << "\n";
        std::cout << "Max users: " << config_.max_users << "\n";
    }
};

// 9. Main with proper error handling
int main() {
    try {
        AppConfig config{
            .max_users = 100,
            .debug_mode = true
        };
        
        Application app(config);
        app.run();
        
        std::cout << "\n=== Application Completed Successfully ===\n";
        return 0;
        
    } catch (const std::exception& e) {
        std::cerr << "FATAL ERROR: " << e.what() << "\n";
        return 1;
    } catch (...) {
        std::cerr << "FATAL ERROR: Unknown exception\n";
        return 2;
    }
}

Best Practices Demonstrated:

Design & Architecture:

Modern C++ Features:

Resource Management:

Error Handling:

Code Quality:

Performance:

This is a production-quality C++ application template!


Back to Start


Part 13 of 22 - Best Practices and Idioms