cpp-know-hows

cpp related stuff

View on GitHub

Advanced C++ Features

Overview

Modern C++ introduces powerful features for resource management, performance optimization, and safer code. This chapter covers move semantics, smart pointers, RAII, perfect forwarding, and more.

┌──────────────────────────────────────────────────────────┐
│              ADVANCED C++ FEATURES                       │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  RESOURCE MGMT    │  PERFORMANCE     │  TYPE SAFETY      │
│  ──────────────   │  ────────────    │  ──────────       │
│  • RAII           │  • Move semantics│  • Strong types   │
│  • Smart pointers │  • RVO/NRVO      │  • std::variant   │
│  • Destructors    │  • Perfect fwd   │  • std::optional  │
│                   │  • Forwarding refs│                  │
│                                                          │
│  MODERN IDIOMS    │  CONCURRENCY     │  ATTRIBUTES       │
│  ──────────────   │  ────────────    │  ──────────       │
│  • Rule of 5/0    │  • std::thread   │  • [[nodiscard]]  │
│  • Copy elision   │  • std::atomic   │  • [[likely]]     │
│  • Value semantics│  • Mutexes       │  • [[maybe_unused]]│
│                                                          │
└──────────────────────────────────────────────────────────┘

RAII (Resource Acquisition Is Initialization)

The RAII Principle

┌────────────────────────────────────────────────────────┐
│                  RAII Pattern                          │
├────────────────────────────────────────────────────────┤
│                                                        │
│  1. Acquire resource in constructor                    │
│  2. Release resource in destructor                     │
│  3. Resource lifetime = Object lifetime                │
│                                                        │
│  Benefits:                                             │
│  • Exception-safe                                      │
│  • No resource leaks                                   │
│  • Automatic cleanup                                   │
│                                                        │
└────────────────────────────────────────────────────────┘

Basic RAII Example

#include <fstream>

// BAD: Manual resource management
void process_file_bad() {
    FILE* file = fopen("data.txt", "r");
    if (!file) return;
    
    // ... process file ...
    // If exception thrown here, file is leaked!
    
    fclose(file);  // Easy to forget!
}

// GOOD: RAII with std::fstream
void process_file_good() {
    std::ifstream file("data.txt");
    if (!file) return;
    
    // ... process file ...
    // File automatically closed when leaving scope
    // Even if exception is thrown!
}  // Destructor closes file

Custom RAII Class

class FileHandle {
    FILE* file_;
    
public:
    explicit FileHandle(const char* filename)
        : file_(fopen(filename, "r")) {
        if (!file_) {
            throw std::runtime_error("Failed to open file");
        }
    }
    
    ~FileHandle() {
        if (file_) {
            fclose(file_);
        }
    }
    
    // Delete copy (file handle shouldn't be copied)
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
    
    // Allow move
    FileHandle(FileHandle&& other) noexcept
        : file_(other.file_) {
        other.file_ = nullptr;
    }
    
    FileHandle& operator=(FileHandle&& other) noexcept {
        if (this != &other) {
            if (file_) fclose(file_);
            file_ = other.file_;
            other.file_ = nullptr;
        }
        return *this;
    }
    
    FILE* get() const { return file_; }
};

RAII for Locks

#include <mutex>

std::mutex mtx;
int shared_data = 0;

// BAD: Manual locking
void increment_bad() {
    mtx.lock();
    shared_data++;
    // If exception thrown, mutex remains locked!
    mtx.unlock();
}

// GOOD: RAII lock guard
void increment_good() {
    std::lock_guard<std::mutex> lock(mtx);
    shared_data++;
    // Mutex automatically unlocked when leaving scope
}

// Also GOOD: Unique lock (more flexible)
void increment_unique() {
    std::unique_lock<std::mutex> lock(mtx);
    shared_data++;
    // Can manually unlock/lock if needed
    lock.unlock();
    // ... do other work ...
    lock.lock();
    shared_data++;
}  // Automatically unlocks if still locked

Move Semantics

Lvalues vs Rvalues

┌────────────────────────────────────────────────────────┐
│              Lvalues vs Rvalues                        │
├────────────────────────────────────────────────────────┤
│                                                        │
│  Lvalue: Has a name, persists beyond expression        │
│    int x = 5;     // x is lvalue                       │
│    x = 10;        // OK: can assign to lvalue          │
│                                                        │
│  Rvalue: Temporary, doesn't persist                    │
│    int y = 5 + 3; // 5+3 is rvalue                     │
│    5 + 3 = 10;    // ERROR: can't assign to rvalue     │
│                                                        │
│  References:                                           │
│    int& lref = x;      // Lvalue reference             │
│    int&& rref = 5 + 3; // Rvalue reference (C++11)     │
│                                                        │
└────────────────────────────────────────────────────────┘

Move Constructor and Move Assignment

#include <utility>
#include <cstring>

class String {
    char* data_;
    size_t size_;
    
public:
    // Constructor
    String(const char* str) {
        size_ = std::strlen(str);
        data_ = new char[size_ + 1];
        std::strcpy(data_, str);
    }
    
    // Destructor
    ~String() {
        delete[] data_;
    }
    
    // Copy constructor (deep copy)
    String(const String& other)
        : size_(other.size_) {
        data_ = new char[size_ + 1];
        std::strcpy(data_, other.data_);
    }
    
    // Copy assignment
    String& operator=(const String& other) {
        if (this != &other) {
            delete[] data_;
            size_ = other.size_;
            data_ = new char[size_ + 1];
            std::strcpy(data_, other.data_);
        }
        return *this;
    }
    
    // Move constructor (C++11)
    String(String&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;
        other.size_ = 0;
    }
    
    // Move assignment (C++11)
    String& operator=(String&& other) noexcept {
        if (this != &other) {
            delete[] data_;
            data_ = other.data_;
            size_ = other.size_;
            other.data_ = nullptr;
            other.size_ = 0;
        }
        return *this;
    }
    
    const char* c_str() const { return data_; }
};

Visual: Move vs Copy

Copy Constructor:
Source:  [ptr] → [H][e][l][l][o]
                    ↓   (copy)
Dest:    [ptr] → [H][e][l][l][o]  (new allocation)

Move Constructor:
Source:  [ptr] → [H][e][l][l][o]
           │ \
           │  \__ (steal pointer)
           ↓     \
Dest:    [ptr] → [H][e][l][l][o]  (no new allocation!)
Source:  [nullptr]  (source is empty but valid)

Cost: Copy = O(n), Move = O(1)

std::move and std::forward

#include <utility>
#include <vector>
#include <string>

int main() {
    std::string s1 = "Hello";
    
    // std::move casts to rvalue reference
    std::string s2 = std::move(s1);
    // s1 is now in "moved-from" state (valid but unspecified)
    // s2 owns the data
    
    std::cout << "s1: " << s1 << '\n';  // May be empty
    std::cout << "s2: " << s2 << '\n';  // "Hello"
    
    // Move into container
    std::vector<std::string> vec;
    vec.push_back(std::move(s2));  // Moves s2 into vector
    
    return 0;
}

When Move Happens Automatically

#include <vector>
#include <string>

std::string make_string() {
    std::string temp = "Hello";
    return temp;  // Move (or RVO), not copy!
}

int main() {
    std::string s = make_string();  // Move, not copy
    
    std::vector<std::string> vec;
    vec.push_back(std::string("World"));  // Temporary moved, not copied
    
    return 0;
}

Smart Pointers

std::unique_ptr

┌────────────────────────────────────────────────────────┐
│                  std::unique_ptr                       │
├────────────────────────────────────────────────────────┤
│                                                        │
│  • Exclusive ownership                                 │
│  • Cannot be copied, only moved                        │
│  • Zero overhead                                       │
│  • Automatic cleanup                                   │
│                                                        │
│  Use when: Single owner needed                         │
│                                                        │
└────────────────────────────────────────────────────────┘
#include <memory>
#include <iostream>

class Widget {
public:
    Widget() { std::cout << "Widget created\n"; }
    ~Widget() { std::cout << "Widget destroyed\n"; }
    void use() { std::cout << "Using widget\n"; }
};

int main() {
    // Create unique_ptr
    std::unique_ptr<Widget> ptr1(new Widget);
    std::unique_ptr<Widget> ptr2 = std::make_unique<Widget>();  // Preferred (C++14)
    
    // Use it
    ptr2->use();
    (*ptr2).use();
    
    // Transfer ownership
    std::unique_ptr<Widget> ptr3 = std::move(ptr2);
    // ptr2 is now nullptr
    
    if (ptr2 == nullptr) {
        std::cout << "ptr2 is null\n";
    }
    
    // Custom deleter
    auto deleter = [](Widget* p) {
        std::cout << "Custom delete\n";
        delete p;
    };
    std::unique_ptr<Widget, decltype(deleter)> ptr4(new Widget, deleter);
    
    // Array version
    std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
    arr[0] = 42;
    
    return 0;
}  // All widgets automatically destroyed

std::shared_ptr

┌────────────────────────────────────────────────────────┐
│                  std::shared_ptr                       │
├────────────────────────────────────────────────────────┤
│                                                        │
│  • Shared ownership via reference counting             │
│  • Can be copied                                       │
│  • Small overhead (ref count)                          │
│  • Thread-safe ref counting                            │
│                                                        │
│  Use when: Multiple owners needed                      │
│                                                        │
└────────────────────────────────────────────────────────┘
#include <memory>

int main() {
    // Create shared_ptr
    std::shared_ptr<Widget> ptr1 = std::make_shared<Widget>();  // Preferred
    std::shared_ptr<Widget> ptr2(new Widget);                   // OK but less efficient
    
    // Share ownership
    std::shared_ptr<Widget> ptr3 = ptr1;  // Copy increases ref count
    std::shared_ptr<Widget> ptr4 = ptr1;  // Ref count = 3
    
    std::cout << "Ref count: " << ptr1.use_count() << '\n';  // 3
    
    ptr3.reset();  // Decreases ref count
    std::cout << "Ref count: " << ptr1.use_count() << '\n';  // 2
    
    // Object destroyed when last shared_ptr is destroyed
    return 0;
}

Visual: shared_ptr Reference Counting

Creation:
ptr1 → [Widget][RefCount:1]

After ptr2 = ptr1:
ptr1 ─┐
      ├→ [Widget][RefCount:2]
ptr2 ─┘

After ptr3 = ptr1:
ptr1 ─┐
ptr2 ─┤→ [Widget][RefCount:3]
ptr3 ─┘

After ptr2.reset():
ptr1 ─┐
      ├→ [Widget][RefCount:2]
ptr3 ─┘

After ptr1 and ptr3 destroyed:
[Widget destroyed]

std::weak_ptr

┌────────────────────────────────────────────────────────┐
│                  std::weak_ptr                         │
├────────────────────────────────────────────────────────┤
│                                                        │
│  • Non-owning observer                                 │
│  • Doesn't affect ref count                            │
│  • Can check if object still exists                    │
│                                                        │
│  Use when: Need to observe without owning              │
│  Solves: Circular reference problem                    │
│                                                        │
└────────────────────────────────────────────────────────┘
#include <memory>

int main() {
    std::weak_ptr<Widget> weak;
    
    {
        std::shared_ptr<Widget> shared = std::make_shared<Widget>();
        weak = shared;  // Weak pointer observes
        
        std::cout << "Ref count: " << shared.use_count() << '\n';  // 1
        
        // Check if still alive and use
        if (auto locked = weak.lock()) {  // Returns shared_ptr
            locked->use();
            std::cout << "Object exists\n";
        }
    }  // shared destroyed
    
    // Object is gone
    if (auto locked = weak.lock()) {
        // Won't execute
    } else {
        std::cout << "Object destroyed\n";
    }
    
    return 0;
}

Circular Reference Problem

struct Node {
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;  // BAD: Circular reference!
    ~Node() { std::cout << "Node destroyed\n"; }
};

// BAD: Memory leak!
void create_cycle() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();
    
    node1->next = node2;
    node2->prev = node1;  // Circular reference
    // Neither node destroyed! Ref counts never reach 0
}

// GOOD: Break cycle with weak_ptr
struct NodeGood {
    std::shared_ptr<NodeGood> next;
    std::weak_ptr<NodeGood> prev;  // GOOD: No ownership
    ~NodeGood() { std::cout << "Node destroyed\n"; }
};

Perfect Forwarding

Forwarding References

template<typename T>
void wrapper(T&& arg) {  // T&& is forwarding reference (universal reference)
    // Forward arg to another function, preserving lvalue/rvalue-ness
    process(std::forward<T>(arg));
}

int main() {
    int x = 42;
    wrapper(x);        // T = int&, arg is lvalue
    wrapper(42);       // T = int, arg is rvalue
    wrapper(std::move(x));  // T = int, arg is rvalue
    
    return 0;
}

Reference Collapsing Rules

┌────────────────────────────────────────────────────────┐
│            Reference Collapsing Rules                  │
├────────────────────────────────────────────────────────┤
│                                                        │
│  T&  &  → T&      (lvalue ref)                        │
│  T&  && → T&      (lvalue ref)                        │
│  T&& &  → T&      (lvalue ref)                        │
│  T&& && → T&&     (rvalue ref)                        │
│                                                        │
│  Result: Only T&& && produces T&&                     │
│                                                        │
└────────────────────────────────────────────────────────┘

Perfect Forwarding Example

#include <utility>
#include <memory>

// Factory function with perfect forwarding
template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

struct Point {
    int x, y;
    Point(int x, int y) : x(x), y(y) {}
    Point(const Point&) { std::cout << "Copy\n"; }
    Point(Point&&) { std::cout << "Move\n"; }
};

int main() {
    // Forwards arguments to Point constructor
    auto p1 = make_unique<Point>(10, 20);
    
    Point temp(1, 2);
    auto p2 = make_unique<Point>(temp);           // Copy
    auto p3 = make_unique<Point>(std::move(temp)); // Move
    
    return 0;
}

Rule of Five / Rule of Zero

Rule of Five

If you define any of these, you should probably define all:
1. Destructor
2. Copy constructor
3. Copy assignment operator
4. Move constructor
5. Move assignment operator
class Resource {
    int* data_;
    
public:
    // 1. Destructor
    ~Resource() {
        delete data_;
    }
    
    // 2. Copy constructor
    Resource(const Resource& other)
        : data_(new int(*other.data_)) {}
    
    // 3. Copy assignment
    Resource& operator=(const Resource& other) {
        if (this != &other) {
            delete data_;
            data_ = new int(*other.data_);
        }
        return *this;
    }
    
    // 4. Move constructor
    Resource(Resource&& other) noexcept
        : data_(other.data_) {
        other.data_ = nullptr;
    }
    
    // 5. Move assignment
    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {
            delete data_;
            data_ = other.data_;
            other.data_ = nullptr;
        }
        return *this;
    }
};

Rule of Zero

If possible, don't define any special member functions.
Use RAII types that manage their own resources.
class BetterResource {
    std::unique_ptr<int> data_;  // Manages itself!
    
public:
    // No need to define any special member functions!
    // Compiler-generated versions work perfectly:
    // - Destructor: unique_ptr cleans up
    // - Move: unique_ptr moves
    // - Copy: deleted automatically (unique_ptr is not copyable)
};

Copy Elision and RVO

Return Value Optimization (RVO)

std::string make_string() {
    std::string result = "Hello";
    // ... modify result ...
    return result;  // RVO: No copy, no move!
}

int main() {
    std::string s = make_string();  // Constructed directly in s
    // No temporary created, no move/copy
    
    return 0;
}

Named Return Value Optimization (NRVO)

// NRVO may be applied (compiler-dependent)
std::vector<int> make_vector() {
    std::vector<int> vec;
    vec.push_back(1);
    vec.push_back(2);
    return vec;  // May be constructed directly at call site
}

Guaranteed Copy Elision (C++17)

// C++17: Guaranteed elision for temporaries
std::string s = std::string("Hello");  // No move, direct construction

Widget get_widget() {
    return Widget();  // Guaranteed: no copy or move
}

// Prvalues (pure rvalues) never create temporaries

Value Categories (C++11/17)

┌────────────────────────────────────────────────────────┐
│                Value Categories                        │
├────────────────────────────────────────────────────────┤
│                                                        │
│                    expression                          │
│                        │                               │
│          ┌─────────────┴─────────────┐                 │
│          ▼                           ▼                 │
│      glvalue                      rvalue               │
│          │                           │                 │
│     ┌────┴────┐               ┌──────┴──────┐          │
│     ▼         ▼               ▼             ▼          │
│  lvalue   xvalue          prvalue                      │
│                                                        │
│  lvalue:  has identity, can't be moved                 │
│  xvalue:  has identity, can be moved                   │
│  prvalue: no identity, can be moved                    │
│                                                        │
└────────────────────────────────────────────────────────┘

Attributes (C++11+)

Common Attributes

// [[nodiscard]] (C++17): Warn if return value ignored
[[nodiscard]] int calculate() {
    return 42;
}

void test() {
    calculate();  // Warning: return value ignored
    int x = calculate();  // OK
}

// [[maybe_unused]]: Suppress unused variable warning
void func([[maybe_unused]] int debug_param) {
    // debug_param used only in debug builds
    #ifdef DEBUG
        std::cout << debug_param << '\n';
    #endif
}

// [[likely]], [[unlikely]] (C++20): Branch prediction hints
int process(int x) {
    if (x > 0) [[likely]] {
        return x * 2;
    } else [[unlikely]] {
        return 0;
    }
}

// [[fallthrough]] (C++17): Intentional switch fallthrough
switch (value) {
    case 1:
        do_something();
        [[fallthrough]];
    case 2:
        do_something_else();
        break;
}

// [[deprecated]] (C++14): Mark as deprecated
[[deprecated("Use new_function() instead")]]
void old_function() {
    // ...
}

// [[noreturn]] (C++11): Function never returns
[[noreturn]] void fatal_error() {
    throw std::runtime_error("Fatal");
}

Inline Variables (C++17)

// Header file
// Before C++17: Problem with multiple definitions
extern const int value;  // Declaration
// cpp file needed: const int value = 42;

// C++17: inline variables
inline const int value = 42;  // Definition in header, OK!

// Especially useful for static members
class Config {
public:
    inline static int max_connections = 100;  // C++17
    inline static const std::string name = "MyApp";
};

Structured Bindings (C++17)

#include <map>
#include <tuple>

int main() {
    // Decompose pair
    std::pair<int, std::string> p(42, "hello");
    auto [num, str] = p;  // num = 42, str = "hello"
    
    // Decompose tuple
    std::tuple<int, double, std::string> t(1, 3.14, "world");
    auto [i, d, s] = t;
    
    // Iterate map
    std::map<std::string, int> ages = { {"Alice", 25}, {"Bob", 30} };
    for (const auto& [name, age] : ages) {
        std::cout << name << ": " << age << '\n';
    }
    
    // Decompose struct
    struct Point { int x, y; };
    Point point{10, 20};
    auto [x, y] = point;
    
    return 0;
}

std::launder (C++17)

Strict Aliasing and Object Lifetime

#include <new>

// Placement new with different type
struct A { int x; };
struct B { int y; };

alignas(B) char buffer[sizeof(B)];

A* a = new (buffer) A{42};
// Object lifetime of A begins

a->~A();  // Destroy A
// Object lifetime of A ends

B* b = new (buffer) B{100};
// Object lifetime of B begins

// Accessing through old pointer is UB
// int val = a->x;  // UB! a no longer valid

// Use std::launder to get valid pointer
A* new_a = std::launder(reinterpret_cast<A*>(buffer));

Best Practices Summary

1. Resource Management

// ALWAYS use RAII
// - Smart pointers for dynamic allocation
// - Lock guards for mutexes
// - unique_ptr for ownership
// - shared_ptr for shared ownership

2. Move Semantics

// Enable move for custom types
// Mark move operations noexcept
// Use std::move explicitly when needed
// Don't std::move on return (RVO)

3. Rule of Five/Zero

// Prefer Rule of Zero
// If managing resources, follow Rule of Five

4. Smart Pointers

// Prefer unique_ptr over shared_ptr
// Use make_unique/make_shared
// Use weak_ptr to break cycles

Common Pitfalls

1. Using Moved-From Objects

std::string s1 = "Hello";
std::string s2 = std::move(s1);
std::cout << s1;  // UB? No, but value is unspecified
s1 = "New value";  // OK: assign new value

2. Returning by const value

// BAD: Prevents move
const std::string bad_function() {
    return std::string("Hello");  // Can't move!
}

// GOOD: Allow move
std::string good_function() {
    return std::string("Hello");  // Can move
}

3. std::move in Return

// BAD: Prevents RVO
std::string bad() {
    std::string s = "Hello";
    return std::move(s);  // Don't do this!
}

// GOOD: Allow RVO
std::string good() {
    std::string s = "Hello";
    return s;  // RVO or move automatically
}

Complete Practical Example: Resource Manager with Advanced Features

Here’s a comprehensive example integrating RAII, move semantics, smart pointers, perfect forwarding, and all advanced C++ features:

#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <utility>
#include <type_traits>
#include <optional>

// 1. RAII wrapper for file handle
class FileHandle {
private:
    FILE* file_;
    std::string filename_;
    
public:
    // Constructor acquires resource
    explicit FileHandle(const std::string& filename, const char* mode = "r")
        : file_(fopen(filename.c_str(), mode)), filename_(filename) {
        if (!file_) {
            throw std::runtime_error("Failed to open file: " + filename);
        }
        std::cout << "Opened file: " << filename_ << "\n";
    }
    
    // Destructor releases resource
    ~FileHandle() {
        if (file_) {
            fclose(file_);
            std::cout << "Closed file: " << filename_ << "\n";
        }
    }
    
    // Delete copy operations
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
    
    // Move constructor
    FileHandle(FileHandle&& other) noexcept
        : file_(other.file_), filename_(std::move(other.filename_)) {
        other.file_ = nullptr;
        std::cout << "Moved file handle: " << filename_ << "\n";
    }
    
    // Move assignment
    FileHandle& operator=(FileHandle&& other) noexcept {
        if (this != &other) {
            // Release current resource
            if (file_) {
                fclose(file_);
            }
            
            // Transfer ownership
            file_ = other.file_;
            filename_ = std::move(other.filename_);
            other.file_ = nullptr;
        }
        return *this;
    }
    
    FILE* get() const { return file_; }
    bool is_open() const { return file_ != nullptr; }
};

// 2. Resource with Rule of Five
class Buffer {
private:
    char* data_;
    size_t size_;
    
public:
    // Constructor
    explicit Buffer(size_t size) : data_(new char[size]), size_(size) {
        std::cout << "Buffer allocated: " << size_ << " bytes\n";
    }
    
    // Destructor
    ~Buffer() {
        delete[] data_;
        std::cout << "Buffer deallocated\n";
    }
    
    // Copy constructor
    Buffer(const Buffer& other) : data_(new char[other.size_]), size_(other.size_) {
        std::copy(other.data_, other.data_ + size_, data_);
        std::cout << "Buffer copied: " << size_ << " bytes\n";
    }
    
    // Copy assignment
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            // Allocate new
            char* new_data = new char[other.size_];
            std::copy(other.data_, other.data_ + other.size_, new_data);
            
            // Release old
            delete[] data_;
            
            // Transfer
            data_ = new_data;
            size_ = other.size_;
            
            std::cout << "Buffer copy-assigned: " << size_ << " bytes\n";
        }
        return *this;
    }
    
    // Move constructor
    Buffer(Buffer&& other) noexcept 
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;
        other.size_ = 0;
        std::cout << "Buffer moved: " << size_ << " bytes\n";
    }
    
    // Move assignment
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            // Release old
            delete[] data_;
            
            // Transfer
            data_ = other.data_;
            size_ = other.size_;
            
            // Reset other
            other.data_ = nullptr;
            other.size_ = 0;
            
            std::cout << "Buffer move-assigned: " << size_ << " bytes\n";
        }
        return *this;
    }
    
    size_t size() const { return size_; }
    char* data() { return data_; }
    const char* data() const { return data_; }
};

// 3. Perfect forwarding factory
template<typename T, typename... Args>
std::unique_ptr<T> make_resource(Args&&... args) {
    std::cout << "Creating resource with " << sizeof...(args) << " arguments\n";
    return std::make_unique<T>(std::forward<Args>(args)...);
}

// 4. Smart pointer hierarchy
class Resource {
protected:
    std::string name_;
    
public:
    explicit Resource(std::string name) : name_(std::move(name)) {
        std::cout << "Resource created: " << name_ << "\n";
    }
    
    virtual ~Resource() {
        std::cout << "Resource destroyed: " << name_ << "\n";
    }
    
    virtual void use() {
        std::cout << "Using resource: " << name_ << "\n";
    }
    
    const std::string& name() const { return name_; }
};

class DatabaseConnection : public Resource {
private:
    bool connected_;
    
public:
    explicit DatabaseConnection(std::string name) 
        : Resource(std::move(name)), connected_(true) {
        std::cout << "  Database connected\n";
    }
    
    ~DatabaseConnection() override {
        disconnect();
    }
    
    void disconnect() {
        if (connected_) {
            std::cout << "  Database disconnected\n";
            connected_ = false;
        }
    }
    
    void use() override {
        if (connected_) {
            std::cout << "Executing query on: " << name_ << "\n";
        }
    }
};

// 5. Resource manager with smart pointers
class ResourceManager {
private:
    std::vector<std::unique_ptr<Resource>> owned_resources_;
    std::vector<std::shared_ptr<Resource>> shared_resources_;
    std::vector<std::weak_ptr<Resource>> observed_resources_;
    
public:
    // Add owned resource (unique ownership)
    void add_owned(std::unique_ptr<Resource> resource) {
        std::cout << "Adding owned resource: " << resource->name() << "\n";
        owned_resources_.push_back(std::move(resource));
    }
    
    // Add shared resource
    std::shared_ptr<Resource> add_shared(std::unique_ptr<Resource> resource) {
        auto shared = std::shared_ptr<Resource>(std::move(resource));
        std::cout << "Adding shared resource: " << shared->name() 
                  << " (refcount: " << shared.use_count() << ")\n";
        shared_resources_.push_back(shared);
        return shared;
    }
    
    // Observe resource without ownership
    void observe(std::shared_ptr<Resource> resource) {
        std::cout << "Observing resource: " << resource->name() << "\n";
        observed_resources_.push_back(std::weak_ptr<Resource>(resource));
    }
    
    // Use all resources
    void use_all() {
        std::cout << "\n--- Using Owned Resources ---\n";
        for (auto& resource : owned_resources_) {
            resource->use();
        }
        
        std::cout << "\n--- Using Shared Resources ---\n";
        for (auto& resource : shared_resources_) {
            resource->use();
            std::cout << "  Refcount: " << resource.use_count() << "\n";
        }
        
        std::cout << "\n--- Checking Observed Resources ---\n";
        for (auto& weak : observed_resources_) {
            if (auto shared = weak.lock()) {
                shared->use();
                std::cout << "  Still alive, refcount: " << shared.use_count() << "\n";
            } else {
                std::cout << "  Resource expired\n";
            }
        }
    }
    
    void clear_shared() {
        std::cout << "\nClearing shared resources...\n";
        shared_resources_.clear();
    }
};

// 6. Value categories demonstration
template<typename T>
void show_value_category(T&& value) {
    using ValueType = std::remove_reference_t<T>;
    
    std::cout << "Value category: ";
    if constexpr (std::is_lvalue_reference_v<T>) {
        std::cout << "lvalue reference\n";
    } else if constexpr (std::is_rvalue_reference_v<T>) {
        std::cout << "rvalue reference\n";
    } else {
        std::cout << "value\n";
    }
}

// 7. Custom deleters
struct CustomDeleter {
    void operator()(Resource* ptr) const {
        std::cout << "Custom deleter called\n";
        delete ptr;
    }
};

// Demonstrations
void demo_raii() {
    std::cout << "\n=== RAII Demo ===\n";
    
    try {
        // Create temporary file for demo
        {
            FILE* f = fopen("/tmp/test.txt", "w");
            if (f) {
                fprintf(f, "Test data");
                fclose(f);
            }
        }
        
        FileHandle file("/tmp/test.txt", "r");
        std::cout << "File is open: " << file.is_open() << "\n";
        
        // File automatically closed when leaving scope
    } catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << "\n";
    }
    
    std::cout << "After scope - file closed automatically\n";
}

void demo_rule_of_five() {
    std::cout << "\n=== Rule of Five Demo ===\n";
    
    Buffer buf1(1024);
    
    // Copy
    Buffer buf2 = buf1;
    
    // Move
    Buffer buf3 = std::move(buf1);
    
    // Copy assignment
    Buffer buf4(512);
    buf4 = buf2;
    
    // Move assignment
    Buffer buf5(256);
    buf5 = std::move(buf2);
}

void demo_smart_pointers() {
    std::cout << "\n=== Smart Pointers Demo ===\n";
    
    ResourceManager manager;
    
    // Unique ownership
    auto db1 = std::make_unique<DatabaseConnection>("DB1");
    manager.add_owned(std::move(db1));
    
    // Shared ownership
    auto db2 = std::make_unique<DatabaseConnection>("DB2");
    auto shared_db2 = manager.add_shared(std::move(db2));
    
    // Additional shared reference
    auto external_ref = shared_db2;
    std::cout << "External reference created, refcount: " 
              << external_ref.use_count() << "\n";
    
    // Observe without owning
    manager.observe(shared_db2);
    
    // Use all resources
    manager.use_all();
    
    // Clear shared resources
    manager.clear_shared();
    
    // Check observed - should be expired
    manager.use_all();
    
    // External reference still valid
    std::cout << "\nExternal reference still valid:\n";
    external_ref->use();
    std::cout << "Refcount: " << external_ref.use_count() << "\n";
}

void demo_perfect_forwarding() {
    std::cout << "\n=== Perfect Forwarding Demo ===\n";
    
    // Create resources with perfect forwarding
    auto res1 = make_resource<Resource>("Resource1");
    auto res2 = make_resource<DatabaseConnection>("DB_Forwarded");
    
    res1->use();
    res2->use();
}

void demo_value_categories() {
    std::cout << "\n=== Value Categories Demo ===\n";
    
    int x = 42;
    
    show_value_category(x);              // lvalue
    show_value_category(std::move(x));   // rvalue
    show_value_category(42);             // rvalue
    show_value_category(x + 1);          // rvalue
}

void demo_custom_deleter() {
    std::cout << "\n=== Custom Deleter Demo ===\n";
    
    std::unique_ptr<Resource, CustomDeleter> res(
        new Resource("CustomDeleted")
    );
    
    res->use();
    
    std::cout << "Leaving scope, custom deleter will be called...\n";
}

void demo_move_semantics() {
    std::cout << "\n=== Move Semantics Demo ===\n";
    
    std::vector<Buffer> buffers;
    
    std::cout << "Adding buffer (move):\n";
    buffers.push_back(Buffer(128));
    
    std::cout << "\nAdding another buffer (move):\n";
    Buffer buf(256);
    buffers.push_back(std::move(buf));
    
    std::cout << "\nOriginal buffer size after move: " << buf.size() << "\n";
}

int main() {
    std::cout << "=== Advanced C++ Features Demo ===\n";
    
    // 1. RAII
    demo_raii();
    
    // 2. Rule of Five
    demo_rule_of_five();
    
    // 3. Smart pointers
    demo_smart_pointers();
    
    // 4. Perfect forwarding
    demo_perfect_forwarding();
    
    // 5. Value categories
    demo_value_categories();
    
    // 6. Custom deleters
    demo_custom_deleter();
    
    // 7. Move semantics
    demo_move_semantics();
    
    std::cout << "\n=== Demo Complete ===\n";
    
    return 0;
}

Concepts Demonstrated:

This example shows modern C++ resource management in action!


Next Steps


Part 12 of 22 - Advanced C++ Features