More Effective C++ 条款29:引用计数
核心思想 :引用计数是一种通过跟踪对象被引用次数来管理资源生命周期的技术,它在多个客户端需要共享相同资源时,可以避免不必要的拷贝,提高效率并简化资源管理。
🚀 1. 问题本质分析
1.1 资源共享的挑战:
- 不必要的拷贝:多个客户端需要相同数据时,深拷贝成本高昂
- 资源释放时机:难以确定何时可以安全释放共享资源
- 内存开销:大量相似对象重复存储相同内容浪费内存
- 一致性维护:多个副本需要保持同步更新
1.2 引用计数的核心需求:
- 共享而非拷贝:允许多个对象共享同一份资源
- 自动生命周期管理:当最后一个使用者释放资源时自动清理
- 写时复制(Copy-on-Write):只在必要时创建副本
- 内存效率:减少重复数据存储
cpp
// 基础示例:简单引用计数实现
class String {
public:
String(const char* initValue = "") {
init(initValue);
}
String(const String& rhs) {
// 共享同一份数据
data = rhs.data;
data->addReference();
}
~String() {
if (data->removeReference() == 0) {
delete data;
}
}
String& operator=(const String& rhs) {
if (this != &rhs) {
if (data->removeReference() == 0) {
delete data;
}
data = rhs.data;
data->addReference();
}
return *this;
}
const char& operator[](int index) const {
return data->chars[index];
}
// 非const版本需要写时复制
char& operator[](int index) {
if (data->isShared()) {
StringValue* newData = new StringValue(data->chars);
data->removeReference();
data = newData;
}
data->markUnshareable();
return data->chars[index];
}
private:
// 引用计数数据结构
struct StringValue {
size_t refCount;
char* chars;
bool shareable;
StringValue(const char* initValue)
: refCount(1), shareable(true) {
chars = new char[strlen(initValue) + 1];
strcpy(chars, initValue);
}
~StringValue() {
delete[] chars;
}
void addReference() {
++refCount;
}
size_t removeReference() {
return --refCount;
}
bool isShared() const {
return refCount > 1;
}
void markUnshareable() {
shareable = false;
}
};
StringValue* data;
void init(const char* initValue) {
data = new StringValue(initValue);
}
};
📦 2. 问题深度解析
2.1 写时复制(Copy-on-Write)优化:
cpp
// 增强的写时复制实现
class COWString {
public:
// ... 构造函数、析构函数、拷贝操作同上
// 安全的非const访问
char& operator[](size_t index) {
// 如果被共享,创建副本
if (data->isShared()) {
// 写时复制:创建新副本
StringValue* newData = new StringValue(data->chars);
data->removeReference();
data = newData;
}
// 标记为不可共享(因为可能被修改)
data->markUnshareable();
return data->chars[index];
}
// 提供const版本避免不必要的复制
const char& operator[](size_t index) const {
return data->chars[index];
}
// 批量修改操作也需要检查共享
void append(const char* str) {
if (data->isShared()) {
StringValue* newData = new StringValue(data->chars);
data->removeReference();
data = newData;
}
// 执行追加操作...
size_t oldLen = strlen(data->chars);
size_t appendLen = strlen(str);
char* newChars = new char[oldLen + appendLen + 1];
strcpy(newChars, data->chars);
strcat(newChars, str);
delete[] data->chars;
data->chars = newChars;
}
private:
struct StringValue {
std::atomic<size_t> refCount; // 原子引用计数
char* chars;
bool shareable;
mutable std::mutex mutex; // 用于线程安全
StringValue(const char* initValue)
: refCount(1), shareable(true) {
chars = new char[strlen(initValue) + 1];
strcpy(chars, initValue);
}
// 线程安全的引用操作
void addReference() {
std::lock_guard<std::mutex> lock(mutex);
++refCount;
}
size_t removeReference() {
std::lock_guard<std::mutex> lock(mutex);
return --refCount;
}
bool isShared() const {
std::lock_guard<std::mutex> lock(mutex);
return refCount > 1;
}
void markUnshareable() {
std::lock_guard<std::mutex> lock(mutex);
shareable = false;
}
};
StringValue* data;
};
2.2 通用引用计数模板:
cpp
// 通用引用计数包装器
template<typename T>
class RCPtr {
public:
RCPtr(T* realPtr = nullptr) : pointee(realPtr) {
init();
}
RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) {
init();
}
~RCPtr() {
if (pointee && pointee->removeReference() == 0) {
delete pointee;
}
}
RCPtr& operator=(const RCPtr& rhs) {
if (pointee != rhs.pointee) {
if (pointee && pointee->removeReference() == 0) {
delete pointee;
}
pointee = rhs.pointee;
init();
}
return *this;
}
T* operator->() const { return pointee; }
T& operator*() const { return *pointee; }
// 写时复制支持
void makeCopy() {
if (pointee->isShared()) {
T* oldPtr = pointee;
pointee = new T(*oldPtr);
oldPtr->removeReference();
pointee->addReference();
}
}
private:
void init() {
if (pointee == nullptr) return;
if (pointee->isShareable() == false) {
pointee = new T(*pointee);
}
pointee->addReference();
}
T* pointee;
};
// 可被引用计数管理的基类
class RCObject {
public:
RCObject() : refCount(0), shareable(true) {}
RCObject(const RCObject&) : refCount(0), shareable(true) {}
virtual ~RCObject() = default;
void addReference() { ++refCount; }
size_t removeReference() { return --refCount; }
size_t getRefCount() const { return refCount; }
bool isShared() const { return refCount > 1; }
bool isShareable() const { return shareable; }
void markUnshareable() { shareable = false; }
private:
size_t refCount;
bool shareable;
};
// 使用示例:可引用计数的字符串
class RCString : public RCObject {
public:
RCString(const char* value = "") {
init(value);
}
RCString(const RCString& rhs) : RCObject(rhs) {
init(rhs.data);
}
~RCString() {
delete[] data;
}
const char* c_str() const { return data; }
size_t length() const { return strlen(data); }
// 修改操作
char& operator[](size_t index) {
// 需要在调用前调用makeCopy()
return data[index];
}
// 只读访问
const char& operator[](size_t index) const {
return data[index];
}
private:
void init(const char* value) {
data = new char[strlen(value) + 1];
strcpy(data, value);
}
char* data;
};
⚖️ 3. 解决方案与最佳实践
3.1 智能指针与引用计数结合:
cpp
// 带引用计数的智能指针
template<typename T>
class RefCountedPtr {
public:
explicit RefCountedPtr(T* ptr = nullptr)
: pointer(ptr), refCount(new size_t(1)) {}
RefCountedPtr(const RefCountedPtr& other)
: pointer(other.pointer), refCount(other.refCount) {
++(*refCount);
}
RefCountedPtr(RefCountedPtr&& other) noexcept
: pointer(other.pointer), refCount(other.refCount) {
other.pointer = nullptr;
other.refCount = nullptr;
}
~RefCountedPtr() {
decrementRefCount();
}
RefCountedPtr& operator=(const RefCountedPtr& other) {
if (this != &other) {
decrementRefCount();
pointer = other.pointer;
refCount = other.refCount;
++(*refCount);
}
return *this;
}
RefCountedPtr& operator=(RefCountedPtr&& other) noexcept {
if (this != &other) {
decrementRefCount();
pointer = other.pointer;
refCount = other.refCount;
other.pointer = nullptr;
other.refCount = nullptr;
}
return *this;
}
T& operator*() const { return *pointer; }
T* operator->() const { return pointer; }
T* get() const { return pointer; }
size_t use_count() const {
return refCount ? *refCount : 0;
}
// 写时复制支持
void make_unique() {
if (use_count() > 1) {
// 创建新副本
T* newPointer = new T(*pointer);
decrementRefCount();
pointer = newPointer;
refCount = new size_t(1);
}
}
private:
void decrementRefCount() {
if (refCount) {
--(*refCount);
if (*refCount == 0) {
delete pointer;
delete refCount;
}
}
}
T* pointer;
size_t* refCount;
};
// 使用示例
class LargeData {
public:
LargeData() { /* 昂贵的初始化 */ }
LargeData(const LargeData&) { /* 昂贵的拷贝 */ }
// ... 其他操作
};
void usageExample() {
RefCountedPtr<LargeData> data1(new LargeData());
RefCountedPtr<LargeData> data2 = data1; // 共享,不拷贝
// 当需要修改时再创建副本
data2.make_unique(); // 现在创建实际副本
}
3.2 线程安全的引用计数:
cpp
// 线程安全的引用计数实现
template<typename T>
class ThreadSafeRefCounted {
public:
ThreadSafeRefCounted() : refCount(1) {}
virtual ~ThreadSafeRefCounted() = default;
void addReference() {
refCount.fetch_add(1, std::memory_order_relaxed);
}
size_t removeReference() {
size_t oldCount = refCount.fetch_sub(1, std::memory_order_acq_rel);
if (oldCount == 1) {
delete this;
}
return oldCount - 1;
}
size_t getRefCount() const {
return refCount.load(std::memory_order_relaxed);
}
protected:
mutable std::atomic<size_t> refCount;
};
// 线程安全的智能指针
template<typename T>
class TSRefCountedPtr {
public:
explicit TSRefCountedPtr(T* ptr = nullptr) : pointer(ptr) {
if (pointer) {
pointer->addReference();
}
}
TSRefCountedPtr(const TSRefCountedPtr& other) : pointer(other.pointer) {
if (pointer) {
pointer->addReference();
}
}
TSRefCountedPtr(TSRefCountedPtr&& other) noexcept : pointer(other.pointer) {
other.pointer = nullptr;
}
~TSRefCountedPtr() {
if (pointer) {
pointer->removeReference();
}
}
TSRefCountedPtr& operator=(const TSRefCountedPtr& other) {
if (this != &other) {
// 先增加新对象的引用
if (other.pointer) {
other.pointer->addReference();
}
// 然后减少当前对象的引用
if (pointer) {
pointer->removeReference();
}
pointer = other.pointer;
}
return *this;
}
// 其他操作类似...
private:
T* pointer;
};
3.3 引用计数在复杂对象中的应用:
cpp
// 复杂对象的引用计数管理
class Document : public ThreadSafeRefCounted {
public:
static Document* create(const std::string& content) {
return new Document(content);
}
// 只读访问
const std::string& getContent() const {
std::lock_guard<std::mutex> lock(mutex);
return content;
}
// 修改操作需要线程安全
void append(const std::string& text) {
std::lock_guard<std::mutex> lock(mutex);
content += text;
++version;
}
size_t getVersion() const {
std::lock_guard<std::mutex> lock(mutex);
return version;
}
// 创建快照(写时复制)
Document* createSnapshot() const {
std::lock_guard<std::mutex> lock(mutex);
return new Document(content, version);
}
private:
Document(const std::string& content)
: content(content), version(0) {}
Document(const std::string& content, size_t ver)
: content(content), version(ver) {}
std::string content;
size_t version;
mutable std::mutex mutex;
};
// 使用智能指针管理文档
class DocumentHandle {
public:
explicit DocumentHandle(Document* doc = nullptr) : document(doc) {}
// 读取内容
std::string read() const {
if (document) {
return document->getContent();
}
return "";
}
// 修改内容(自动处理引用计数)
void write(const std::string& text) {
if (document && document->getRefCount() > 1) {
// 如果有其他引用,创建副本
Document* newDoc = document->createSnapshot();
document->removeReference();
document = newDoc;
}
if (document) {
document->append(text);
}
}
// 创建新版本
DocumentHandle createBranch() const {
if (document) {
Document* newDoc = document->createSnapshot();
return DocumentHandle(newDoc);
}
return DocumentHandle();
}
private:
Document* document;
};
💡 关键实践原则
- 识别共享场景
当多个对象需要相同数据且读取远多于写入时,引用计数特别有效 - 写时复制优化
延迟拷贝直到真正需要修改时,避免不必要的复制开销 - 线程安全考虑
在多线程环境中使用原子操作确保引用计数安全 - 避免循环引用
引用计数无法处理循环引用,需要配合弱引用或其他机制 - 性能权衡
引用计数增加开销,适用于拷贝成本高而共享收益大的场景
引用计数 vs 垃圾收集:
cpp// 引用计数的优点: // 1. 确定性销毁:资源立即释放 // 2. 预测性性能:没有GC暂停 // 3. 更少的内存开销:不需要GC数据结构 // 引用计数的缺点: // 1. 循环引用问题 // 2. 原子操作开销 // 3. 代码复杂性
现代C++中的引用计数:
cpp// std::shared_ptr 已经内置引用计数 void modernExample() { std::shared_ptr<LargeData> data1 = std::make_shared<LargeData>(); std::shared_ptr<LargeData> data2 = data1; // 共享引用 // 如果需要修改且避免影响其他使用者 if (!data2.unique()) { data2 = std::make_shared<LargeData>(*data2); // 深拷贝 } // 现在可以安全修改data2 }
总结:
引用计数是一种强大的资源管理技术,通过在多个使用者间共享资源而非复制,显著提高内存效率和性能。
关键实现技术包括写时复制、原子操作确保线程安全、以及适当的抽象来隐藏复杂性。现代C++中std::shared_ptr提供了内置的引用计数,但在特定场景下自定义实现可能更有优势。
正确使用引用计数可以大幅减少不必要的资源拷贝,同时保持代码的清晰性和资源管理的可靠性。但在使用时需要注意循环引用问题和性能权衡。