5More Effective C++

技术

编程技巧

代理类引用计数写时复制句柄类

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第5章:技术(Techniques)

一、导读

1.1 本章学习目标

第5章讲解 C++ 的高级编程技术:代理类、引用计数、写时复制、句柄类等。这些技术帮助解决复杂的设计问题。

本章核心学习目标:

  • 掌握代理类(Proxy):用代理对象替代真实对象
  • 理解引用计数:共享资源的生命周期管理
  • 掌握写时复制(Copy-on-Write):延迟拷贝优化
  • 理解句柄类(Handle):类似智能指针的管理机制

1.2 为什么本章如此重要

这些高级技术是构建复杂 C++ 系统的基础工具。理解它们能帮助你设计更灵活、更高效的代码。

1.3 核心问题引导

代理类如何解决类型不匹配的问题?

引用计数如何管理共享资源?

写时复制如何平衡读写性能?


二、核心概念详解

2.1 代理类

cpp
// 问题: 二维数组的 operator[] 返回什么?
// arr[i][j] = value; 需要 arr[i] 返回一个可以再次 [] 的对象

// 解决方案: 代理类
template<typename T>
class Matrix {
    std::vector<std::vector<T>> data;
    
    // 代理类: 代表一行
    class RowProxy {
        std::vector<T>& row;
    public:
        RowProxy(std::vector<T>& r) : row(r) {}
        
        T& operator[](int col) { return row[col]; }
        const T& operator[](int col) const { return row[col]; }
    };
    
public:
    RowProxy operator[](int row) { return RowProxy(data[row]); }
    const RowProxy operator[](int row) const { return RowProxy(data[row]); }
};

Matrix<int> m(3, 3);
m[1][2] = 42;  // m[1] 返回 RowProxy,RowProxy[2] 返回实际元素

2.2 引用计数

cpp
template<typename T>
class SharedPtr {
    struct ControlBlock {
        T* ptr;
        std::atomic<int> count;
        ControlBlock(T* p) : ptr(p), count(1) {}
    };
    
    ControlBlock* cb;
    
public:
    explicit SharedPtr(T* p = nullptr) : cb(new ControlBlock(p)) {}
    
    SharedPtr(const SharedPtr& other) : cb(other.cb) {
        cb->count.fetch_add(1);
    }
    
    ~SharedPtr() {
        if (--cb->count == 0) {
            delete cb->ptr;
            delete cb;
        }
    }
    
    SharedPtr& operator=(const SharedPtr& other) {
        if (this != &other) {
            if (--cb->count == 0) {
                delete cb->ptr;
                delete cb;
            }
            cb = other.cb;
            cb->count.fetch_add(1);
        }
        return *this;
    }
    
    T& operator*() const { return *cb->ptr; }
    T* operator->() const { return cb->ptr; }
};

2.3 写时复制(Copy-on-Write)

cpp
class CowString {
    struct StringRep {
        std::string data;
        int refCount;
        StringRep(const std::string& s) : data(s), refCount(1) {}
    };
    
    StringRep* rep;
    
public:
    CowString(const std::string& s = "") : rep(new StringRep(s)) {}
    
    CowString(const CowString& other) : rep(other.rep) {
        rep->refCount++;
    }
    
    ~CowString() {
        if (--rep->refCount == 0) delete rep;
    }
    
    // 读取: 不拷贝
    const char& operator[](int i) const {
        return rep->data[i];
    }
    
    // 写入: 如果引用计数 > 1,先拷贝
    char& operator[](int i) {
        if (rep->refCount > 1) {
            rep->refCount--;
            rep = new StringRep(rep->data);
        }
        return rep->data[i];
    }
};

CowString s1 = "Hello";
CowString s2 = s1;  // 共享数据,不拷贝
s2[0] = 'h';        // 此时才拷贝(写时复制)

三、实战案例

3.1 案例一:引用计数的字符串池

cpp
class StringPool {
    std::unordered_map<std::string, int> pool;
    std::mutex mtx;
    
public:
    // 获取字符串的引用计数版本
    const std::string& intern(const std::string& s) {
        std::lock_guard<std::mutex> lock(mtx);
        auto it = pool.find(s);
        if (it != pool.end()) {
            it->second++;
            return it->first;
        }
        auto result = pool.emplace(s, 1);
        return result.first->first;
    }
    
    void release(const std::string& s) {
        std::lock_guard<std::mutex> lock(mtx);
        auto it = pool.find(s);
        if (it != pool.end() && --it->second == 0) {
            pool.erase(it);
        }
    }
};

四、本章小结

代理类解决类型不匹配问题,实现多维数组等复杂接口

引用计数管理共享资源的生命周期

写时复制延迟拷贝直到修改,平衡读写性能


五、思考与练习

思考题

引用计数的线程安全如何实现?

写时复制在多线程环境下的挑战是什么?

编程练习

实现一个线程安全的引用计数智能指针。

实现一个写时复制的字符串类。


5/6技术
0%