C++本身没有内置的垃圾回收机制,开发者需要手动管理动态分配的内存,长期开发过程中很容易出现内存泄漏、重复释放、悬空指针等问题。引用计数是实现简单垃圾回收的常用思路,核心逻辑是为每个管理的对象维护一个引用计数,当计数归零时自动释放对象内存,结合智能指针的封装可以让内存管理过程自动化。

引用计数GC的核心原理
引用计数GC的实现依赖三个核心规则:
- 当一个新的指针指向被管理的对象时,对象的引用计数加1
- 当指向对象的指针被销毁或者指向其他对象时,对象的引用计数减1
- 当对象的引用计数归零时,说明没有任何指针再引用该对象,此时自动释放对象占用的内存
自定义智能指针实现简单GC
我们可以通过封装一个带引用计数的智能指针类,来实现基础的垃圾回收功能,核心需要维护两个指针:一个指向实际管理的对象,一个指向引用计数变量。
智能指针类的定义
首先定义智能指针的模板类,支持任意类型的对象管理:
#include <iostream>
template <typename T>
class RefCountPtr {
private:
T* obj; // 指向管理的对象
int* ref_count; // 指向引用计数变量
public:
// 构造函数,初始化对象和引用计数
explicit RefCountPtr(T* p = nullptr) : obj(p), ref_count(nullptr) {
if (obj != nullptr) {
ref_count = new int(1); // 新对象初始引用计数为1
}
}
// 拷贝构造函数,引用计数加1
RefCountPtr(const RefCountPtr<T>& other) : obj(other.obj), ref_count(other.ref_count) {
if (ref_count != nullptr) {
(*ref_count)++;
}
}
// 析构函数,引用计数减1,归零则释放内存
~RefCountPtr() {
release();
}
// 拷贝赋值运算符
RefCountPtr<T>& operator=(const RefCountPtr<T>& other) {
if (this == &other) {
return *this;
}
// 先释放当前指向的对象
release();
// 指向新的对象,引用计数加1
obj = other.obj;
ref_count = other.ref_count;
if (ref_count != nullptr) {
(*ref_count)++;
}
return *this;
}
// 重载->运算符,方便访问对象成员
T* operator->() const {
return obj;
}
// 重载*运算符,方便解引用
T& operator*() const {
return *obj;
}
// 获取当前引用计数
int getRefCount() const {
if (ref_count == nullptr) {
return 0;
}
return *ref_count;
}
private:
void release() {
if (ref_count != nullptr) {
(*ref_count)--;
// 引用计数归零,释放对象和计数变量
if (*ref_count == 0) {
delete obj;
delete ref_count;
}
}
obj = nullptr;
ref_count = nullptr;
}
};
功能测试示例
我们定义一个简单的测试类,验证智能指针的垃圾回收效果:
// 测试类,析构时输出提示
class TestObj {
public:
int value;
TestObj(int v) : value(v) {
std::cout << "TestObj构造,value=" << value << std::endl;
}
~TestObj() {
std::cout << "TestObj析构,value=" << value << std::endl;
}
};
int main() {
// 创建智能指针管理TestObj对象
RefCountPtr<TestObj> ptr1(new TestObj(10));
std::cout << "ptr1引用计数:" << ptr1.getRefCount() << std::endl;
// 拷贝构造,引用计数加1
RefCountPtr<TestObj> ptr2 = ptr1;
std::cout << "拷贝后ptr1引用计数:" << ptr1.getRefCount() << std::endl;
std::cout << "ptr2引用计数:" << ptr2.getRefCount() << std::endl;
// 访问对象成员
std::cout << "ptr1指向的对象value:" << ptr1->value << std::endl;
std::cout << "ptr2指向的对象value:" << (*ptr2).value << std::endl;
// ptr1离开作用域,引用计数减1
{
RefCountPtr<TestObj> ptr3 = ptr2;
std::cout << "ptr3创建后引用计数:" << ptr1.getRefCount() << std::endl;
}
// ptr3析构,引用计数减1
std::cout << "ptr3析构后引用计数:" << ptr1.getRefCount() << std::endl;
// ptr1和ptr2析构,引用计数归零,对象自动释放
return 0;
}
运行上述代码,输出结果如下:
TestObj构造,value=10 ptr1引用计数:1 拷贝后ptr1引用计数:2 ptr2引用计数:2 ptr1指向的对象value:10 ptr2指向的对象value:10 ptr3创建后引用计数:3 ptr3析构后引用计数:2 TestObj析构,value=10
简单引用计数GC的局限性
这种基于引用计数的简单GC机制虽然能解决大部分内存管理问题,但存在两个明显缺陷:
- 无法处理循环引用问题:如果两个对象互相持有对方的智能指针,引用计数永远不会归零,会导致内存泄漏
- 引用计数的增减需要额外的内存开销和运算开销,在频繁拷贝指针的场景下会有一定的性能损耗
实际C++标准库中的std::shared_ptr就是基于引用计数实现的智能指针,同时提供了std::weak_ptr来解决循环引用问题,开发者可以直接使用标准库组件,不需要自己重复实现基础功能。