C++ 编程学习笔记

智能指针深度解析

1. 智能指针的必要性

在传统 C++ 编程中,手动管理内存容易导致内存泄漏、悬空指针等问题。C++11 引入的智能指针通过 RAII(Resource Acquisition Is Initialization)机制,自动管理动态分配的内存,极大地提高了代码的安全性和可维护性。

2. unique_ptr - 独占所有权

unique_ptr 是一种独占式智能指针,确保同一时刻只有一个指针拥有对象的所有权。当 unique_ptr 离开作用域时,它会自动释放所管理的对象。

#include <memory>
#include <iostream>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
    void doSomething() { std::cout << "Doing something\n"; }
};

void uniquePtrExample() {
    // 创建 unique_ptr
    std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>();
    ptr1->doSomething();
    
    // 转移所有权
    std::unique_ptr<Resource> ptr2 = std::move(ptr1);
    // ptr1 现在为空
    
    if (!ptr1) {
        std::cout << "ptr1 is null\n";
    }
    
    // 使用数组
    std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
    arr[0] = 42;
}
// Resource 会在函数结束时自动释放
注意:unique_ptr 不可复制,只能移动。使用 std::move 转移所有权时,原指针会变为空指针。

3. shared_ptr - 共享所有权

shared_ptr 允许多个指针共享同一个对象的所有权,通过引用计数机制管理对象的生命周期。当最后一个 shared_ptr 被销毁时,对象才会被释放。

#include <memory>
#include <vector>

class Node {
public:
    int value;
    std::shared_ptr<Node> next;
    
    Node(int v) : value(v) {}
    ~Node() { std::cout << "Node " << value << " destroyed\n"; }
};

void sharedPtrExample() {
    std::shared_ptr<Node> node1 = std::make_shared<Node>(1);
    std::shared_ptr<Node> node2 = std::make_shared<Node>(2);
    
    // 共享所有权
    std::shared_ptr<Node> node1_copy = node1;
    
    std::cout << "node1 引用计数: " << node1.use_count() << "\n"; // 输出 2
    
    // 创建链表
    node1->next = node2;
    
    // 可以在容器中使用
    std::vector<std::shared_ptr<Node>> nodes;
    nodes.push_back(node1);
    nodes.push_back(node2);
}

4. weak_ptr - 打破循环引用

weak_ptr 是一种不控制对象生命周期的智能指针,主要用于解决 shared_ptr 的循环引用问题。它可以观察对象但不增加引用计数。

class TreeNode {
public:
    int value;
    std::shared_ptr<TreeNode> left;
    std::shared_ptr<TreeNode> right;
    std::weak_ptr<TreeNode> parent;  // 使用 weak_ptr 避免循环引用
    
    TreeNode(int v) : value(v) {}
};

void weakPtrExample() {
    auto root = std::make_shared<TreeNode>(1);
    auto leftChild = std::make_shared<TreeNode>(2);
    
    root->left = leftChild;
    leftChild->parent = root;  // weak_ptr 不增加引用计数
    
    // 使用 weak_ptr
    if (auto parent = leftChild->parent.lock()) {
        std::cout << "Parent value: " << parent->value << "\n";
    }
}
提示:使用 weak_ptr::lock() 方法可以安全地访问对象,如果对象已被销毁,会返回空指针。

5. 自定义删除器

智能指针支持自定义删除器,用于处理特殊的资源释放逻辑。

void customDeleterExample() {
    // 自定义删除器用于文件句柄
    auto fileDeleter = [](FILE* fp) {
        if (fp) {
            std::cout << "Closing file\n";
            fclose(fp);
        }
    };
    
    std::unique_ptr<FILE, decltype(fileDeleter)> file(
        fopen("data.txt", "r"), 
        fileDeleter
    );
    
    // shared_ptr 的自定义删除器
    std::shared_ptr<int> ptr(new int[10], [](int* p) {
        std::cout << "Deleting array\n";
        delete[] p;
    });
}

模板编程技术

1. 函数模板

函数模板允许我们编写类型无关的通用代码,编译器会根据实际使用的类型生成具体的函数实例。

// 基础函数模板
template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

// 使用多个模板参数
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

// 模板特化
template<>
const char* max<const char*>(const char* a, const char* b) {
    return (strcmp(a, b) > 0) ? a : b;
}

void templateExample() {
    int i = max(10, 20);           // T = int
    double d = max(3.14, 2.71);    // T = double
    
    auto result = add(10, 3.14);   // 混合类型运算
}

2. 类模板

类模板用于创建通用的数据结构和容器类。

template<typename T, size_t Size>
class Array {
private:
    T data[Size];
    
public:
    size_t size() const { return Size; }
    
    T& operator[](size_t index) {
        if (index >= Size) {
            throw std::out_of_range("Index out of range");
        }
        return data[index];
    }
    
    const T& operator[](size_t index) const {
        if (index >= Size) {
            throw std::out_of_range("Index out of range");
        }
        return data[index];
    }
    
    // 迭代器支持
    T* begin() { return data; }
    T* end() { return data + Size; }
};

void arrayTemplateExample() {
    Array<int, 5> intArray;
    intArray[0] = 10;
    
    Array<std::string, 3> strArray;
    strArray[0] = "Hello";
}

3. 变参模板

C++11 引入的变参模板允许接受任意数量的模板参数。

// 递归终止函数
void print() {
    std::cout << "\n";
}

// 变参模板函数
template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    print(args...);  // 递归调用
}

// 使用折叠表达式(C++17)
template<typename... Args>
auto sum(Args... args) {
    return (args + ...);
}

void variadicExample() {
    print(1, 2.5, "hello", 'c');  // 输出: 1 2.5 hello c
    
    auto total = sum(1, 2, 3, 4, 5);  // 输出: 15
}

4. SFINAE 和类型萃取

SFINAE(Substitution Failure Is Not An Error)是 C++ 模板元编程的重要技术。

#include <type_traits>

// 使用 enable_if 进行条件编译
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
processInteger(T value) {
    return value * 2;
}

template<typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
processFloat(T value) {
    return value * 1.5;
}

// C++17 if constexpr
template<typename T>
auto process(T value) {
    if constexpr (std::is_integral_v<T>) {
        return value * 2;
    } else if constexpr (std::is_floating_point_v<T>) {
        return value * 1.5;
    } else {
        return value;
    }
}

并发编程

1. 线程基础

C++11 引入了标准的线程库,提供了跨平台的并发编程支持。

#include <thread>
#include <iostream>
#include <vector>

void threadFunction(int id) {
    std::cout << "Thread " << id << " is running\n";
}

void threadExample() {
    std::vector<std::thread> threads;
    
    // 创建多个线程
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(threadFunction, i);
    }
    
    // 等待所有线程完成
    for (auto& t : threads) {
        if (t.joinable()) {
            t.join();
        }
    }
    
    // 使用 lambda
    std::thread t([](int x, int y) {
        std::cout << "Result: " << x + y << "\n";
    }, 10, 20);
    t.join();
}

2. 互斥量和锁

使用互斥量保护共享数据,防止数据竞争。

#include <mutex>

class Counter {
private:
    int count = 0;
    mutable std::mutex mtx;
    
public:
    void increment() {
        std::lock_guard<std::mutex> lock(mtx);
        ++count;
    }
    
    int getCount() const {
        std::lock_guard<std::mutex> lock(mtx);
        return count;
    }
    
    // 使用 unique_lock 支持更灵活的锁定
    void complexOperation() {
        std::unique_lock<std::mutex> lock(mtx);
        // 执行一些操作
        count += 10;
        lock.unlock();  // 提前释放锁
        
        // 执行不需要锁保护的操作
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        
        lock.lock();  // 重新获取锁
        count += 5;
    }
};

3. 条件变量

条件变量用于线程间的同步,实现生产者-消费者模式。

#include <condition_variable>
#include <queue>

template<typename T>
class ThreadSafeQueue {
private:
    mutable std::mutex mtx;
    std::queue<T> queue;
    std::condition_variable cv;
    
public:
    void push(T value) {
        std::lock_guard<std::mutex> lock(mtx);
        queue.push(std::move(value));
        cv.notify_one();
    }
    
    bool tryPop(T& value) {
        std::lock_guard<std::mutex> lock(mtx);
        if (queue.empty()) {
            return false;
        }
        value = std::move(queue.front());
        queue.pop();
        return true;
    }
    
    void waitAndPop(T& value) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this] { return !queue.empty(); });
        value = std::move(queue.front());
        queue.pop();
    }
};

4. 原子操作

对于简单的数据类型,可以使用原子操作避免使用锁。

#include <atomic>

class AtomicCounter {
private:
    std::atomic<int> count{0};
    
public:
    void increment() {
        count.fetch_add(1, std::memory_order_relaxed);
    }
    
    int getCount() const {
        return count.load(std::memory_order_relaxed);
    }
    
    bool compareAndSwap(int expected, int desired) {
        return count.compare_exchange_strong(
            expected, 
            desired, 
            std::memory_order_release,
            std::memory_order_acquire
        );
    }
};
警告:并发编程容易出现死锁、数据竞争等问题,必须仔细设计锁的获取顺序,并充分测试多线程代码。

移动语义与右值引用

1. 左值与右值

左值(lvalue)是持久存在的对象,右值(rvalue)是临时对象。C++11 引入右值引用(&&)支持移动语义,避免不必要的拷贝。

class Buffer {
private:
    size_t size;
    char* data;
    
public:
    // 构造函数
    Buffer(size_t s) : size(s), data(new char[s]) {
        std::cout << "Constructor\n";
    }
    
    // 拷贝构造函数
    Buffer(const Buffer& other) : size(other.size), data(new char[other.size]) {
        std::cout << "Copy constructor\n";
        std::memcpy(data, other.data, size);
    }
    
    // 移动构造函数
    Buffer(Buffer&& other) noexcept : size(other.size), data(other.data) {
        std::cout << "Move constructor\n";
        other.data = nullptr;
        other.size = 0;
    }
    
    // 拷贝赋值运算符
    Buffer& operator=(const Buffer& other) {
        std::cout << "Copy assignment\n";
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = new char[size];
            std::memcpy(data, other.data, size);
        }
        return *this;
    }
    
    // 移动赋值运算符
    Buffer& operator=(Buffer&& other) noexcept {
        std::cout << "Move assignment\n";
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = other.data;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
    
    ~Buffer() {
        delete[] data;
    }
};

void moveExample() {
    Buffer b1(1024);
    Buffer b2 = std::move(b1);  // 调用移动构造函数
    
    Buffer b3(512);
    b3 = std::move(b2);  // 调用移动赋值运算符
}

2. 完美转发

使用 std::forward 实现参数的完美转发,保持参数的值类别。

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

class Widget {
public:
    Widget(int x, const std::string& s) {
        std::cout << "Constructor: " << x << ", " << s << "\n";
    }
};

void forwardExample() {
    auto ptr = make_unique<Widget>(42, "hello");
}