第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);
}
}
};四、本章小结
代理类解决类型不匹配问题,实现多维数组等复杂接口
引用计数管理共享资源的生命周期
写时复制延迟拷贝直到修改,平衡读写性能
五、思考与练习
思考题
引用计数的线程安全如何实现?
写时复制在多线程环境下的挑战是什么?
编程练习
实现一个线程安全的引用计数智能指针。
实现一个写时复制的字符串类。