文章目录
- [1. 智能指针的使用场景分析](#1. 智能指针的使用场景分析)
- [2. RAII和智能指针的设计思路](#2. RAII和智能指针的设计思路)
- [3. C++标准库智能指针的使用](#3. C++标准库智能指针的使用)
- [4. 智能指针的原理](#4. 智能指针的原理)
1. 智能指针的使用场景分析
下面程序中我们可以看到,new了以后,我们也delete了,但是因为抛异常导,后面的delete没有得到执行,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出,但是因为new本身也可能抛异常,连续的两个new和下面的Divide都可能会抛异常,让我们处理起来很麻烦。智能指针放到这样的场景里面就让问题简单多了。
cpp
// 除法函数,可能抛出异常
double Divide(int a, int b)
{
// 除数为0时抛出字符串异常
if (b == 0)
{
throw "Divide by zero condition!";
}
else
{
return (double)a / (double)b;
}
}
void Func()
{
// 这里演示了资源管理的问题:如果发生异常,需要确保资源正确释放
// 不使用智能指针的问题:需要在多个地方重复释放资源,且容易遗漏
int* array1 = new int[10];
int* array2 = new int[10]; // 如果这里分配失败会抛异常
try
{
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl; // 可能抛出除0异常
}
catch (...) // 捕获所有类型的异常
{
// 异常处理中确保释放资源
cout << "delete []" << array1 << endl;
cout << "delete []" << array2 << endl;
delete[] array1;
delete[] array2;
throw; // 重新抛出异常,保持异常的原始类型
}
// 正常执行路径下的资源释放
cout << "delete []" << array1 << endl;
delete[] array1;
cout << "delete []" << array2 << endl;
delete[] array2;
}
int main()
{
try
{
Func();
}
// 分层次的异常捕获
catch (const char* errmsg) // 捕获字符串异常(除0错误)
{
cout << errmsg << endl;
}
catch (const exception& e) // 捕获标准异常
{
cout << e.what() << endl;
}
catch (...) // 捕获其他所有类型的异常
{
cout << "未知异常" << endl;
}
return 0;
}
2. RAII和智能指针的设计思路
- RAII是Resource Acquisition Is Initialization的缩写,他是一种管理资源的类的设计思想,本质是一种利用对象生命周期来管理获取到的动态资源,避免资源泄漏,这里的资源可以是内存、文件指针、网络连接、互斥锁等等。RAII在获取资源时把资源委托给一个对象,接着控制对资源的访问,资源在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。
- 智能指针类除了满足RAII的设计思路,还要方便资源的访问,所以智能指针类还会想迭代器类一样,重载
operator*/operator->/operator[]
等运算符,方便访问资源。
代码1:
cpp
// SmartPtr类:一个简单的智能指针模板类实现
template<class T>
class SmartPtr
{
public:
// 构造函数:接收一个原始指针
// RAII原则:Resource Acquisition Is Initialization(资源获取即初始化)
// 当对象构造时获取资源,在对象析构时释放资源
// 防止资源泄露,保证异常安全
SmartPtr(T* ptr)
:_ptr(ptr) // 初始化成员指针
{}
// 析构函数:当智能指针对象销毁时自动调用
// 负责释放所管理的内存资源
~SmartPtr()
{
cout << "delete:" << _ptr << endl; // 打印被删除的指针地址
delete _ptr; // 释放内存
}
// 重载操作符*
// 使智能指针可以像普通指针一样,通过*访问所指向的对象
T& operator*()
{
return *_ptr;
}
// 重载操作符->
// 使智能指针可以像普通指针一样通过->访问成员
T* operator->()
{
return _ptr;
}
private:
T* _ptr; // 存储原始指针的成员变量
};
// 测试函数
void f()
{
// 创建管理pair<string, string>类型的智能指针对象
SmartPtr<pair<string, string>> sp1(new pair<string, string>("1111", "22222"));
SmartPtr<pair<string, string>> sp2(new pair<string, string>);
SmartPtr<pair<string, string>> sp3(new pair<string, string>);
// 创建管理string类型的智能指针对象
SmartPtr<string> sp4(new string("xxxxx"));
// 使用重载的操作符访问数据
cout << *sp4 << endl; // 通过*操作符访问string内容,打印xxxxx
cout << sp1->first << endl; // 通过->操作符访问pair的first成员,打印1111
cout << sp1->second << endl; // 通过->操作符访问pair的second成员,打印22222
div(); // 调用某个分隔函数(代码中未给出具体实现)
}
// 主函数:包含异常处理
int main()
{
try
{
f(); // 调用测试函数
}
catch (const exception& e) // 捕获并处理标准异常
{
cout << e.what() << endl; // 打印异常信息
}
return 0;
}
代码2:
cpp
// SmartPtr类:一个简单的智能指针模板类实现
template<class T>
class SmartPtr
{
public:
// 构造函数:接收一个原始指针
// RAII原则:Resource Acquisition Is Initialization(资源获取即初始化)
// 当对象构造时获取资源,在对象析构时释放资源
// 防止资源泄露,保证异常安全
SmartPtr(T* ptr)
:_ptr(ptr) // 初始化成员指针
{}
// 析构函数:当智能指针对象销毁时自动调用
// 负责释放所管理的内存资源
~SmartPtr()
{
cout << "delete:" << _ptr << endl; // 打印被删除的指针地址
delete _ptr; // 释放内存
}
// 重载操作符*
// 使智能指针可以像普通指针一样,通过*访问所指向的对象
T& operator*()
{
return *_ptr;
}
// 重载操作符->
// 使智能指针可以像普通指针一样通过->访问成员
T* operator->()
{
return _ptr;
}
private:
T* _ptr; // 存储原始指针的成员变量
};
// C++98 auto_ptr
// 管理权转移的智能指针,已被废弃
// 缺陷:管理权转移后,原指针悬空,可能导致访问空指针
// C++11 unique_ptr
// 独占所有权的智能指针
// 特点:不允许拷贝,只允许移动,避免了auto_ptr的问题
// C++11 shared_ptr
// 共享所有权的智能指针
// 特点:使用引用计数,多个指针可以共享同一块资源
// C++11 weak_ptr
// 弱引用智能指针
// 特点:配合shared_ptr使用,不增加引用计数,用于解决循环引用问题
int main()
{
SmartPtr<string> sp1(new string("xxxxx"));
SmartPtr<string> sp2(new string("yyyyy"));
// 问题代码:这里会导致严重问题
// 1. 浅拷贝:sp2的指针直接赋值给sp1
// 2. 原有sp1的资源没有释放,导致内存泄漏
// 3. sp1和sp2指向同一块内存
// 4. 当sp1和sp2析构时,同一块内存会被删除两次,导致程序崩溃
sp1 = sp2; // 这是一个有问题的赋值操作!
return 0;
}
这就是为什么我们需要使用标准库提供的智能指针:
unique_ptr
:适用于独占资源的场景shared_ptr
:适用于资源需要共享的场景weak_ptr
:适用于需要解决循环引用的场景
代码3:
cpp
// 智能指针模板类,用于自动管理动态分配的内存
template<class T>
class SmartPtr
{
public:
// 构造函数,接管原始指针
// RAII(Resource Acquisition Is Initialization)模式
// 在构造时获取资源,在析构时释放资源
SmartPtr(T* ptr)
:_ptr(ptr)
{}
// 析构函数,自动释放管理的内存
// 保证即使发生异常,资源也能被正确释放
~SmartPtr()
{
cout << "delete[] " << _ptr << endl;
delete[] _ptr;
}
// 重载解引用运算符
// 使智能指针能像普通指针一样通过*访问数据
T& operator*()
{
return *_ptr;
}
// 重载箭头运算符
// 使智能指针能像普通指针一样通过->访问成员
T* operator->()
{
return _ptr;
}
// 重载下标运算符
// 支持数组访问语法
T& operator[](size_t i)
{
return _ptr[i];
}
private:
T* _ptr; // 实际管理的原始指针
};
// 除法函数,可能抛出异常
double Divide(int a, int b)
{
// 除数为0时抛出异常
if (b == 0)
{
throw "Divide by zero condition!";
}
else
{
return (double)a / (double)b;
}
}
void Func()
{
// 使用智能指针管理动态数组
// 不需要手动释放内存,智能指针会在函数结束或发生异常时自动释放
SmartPtr<int> sp1 = new int[10];
SmartPtr<int> sp2 = new int[10];
// 通过重载的[]运算符访问数组元素
for (size_t i = 0; i < 10; i++)
{
sp1[i] = sp2[i] = i;
}
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
// 函数结束时,sp1和sp2的析构函数会自动调用,释放内存
}
int main()
{
try
{
Func();
}
// 异常处理
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
3. C++标准库智能指针的使用
- C++标准库中的智能指针都在
<memory>
这个头文件下面,我们包含<memory>
就可以是使用了,智能指针有好几种,除了weak_ptr
他们都符合RAII和像指针一样访问的行为,原理上而言主要是解决智能指针拷贝时的思路不同。- auto_ptr 是C++98时设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源的管理权转移给拷贝对象,这是一个非常糟糕的设计,因为他会到被拷贝对象悬空,访问报错的问题,C++11设计出新的智能指针后,强烈建议不要使用
auto_ptr
。其他C++11出来之前很多公司也是明令禁止使用这个智能指针的。- unique_ptr 是C++11设计出来的智能指针,他的名字翻译出来是唯一指针,他的特点的不支持拷贝,只支持移动。如果不需要拷贝的场景就非常建议使用他。
- shared_ptr 是C++11设计出来的智能指针,他的名字翻译出来是共享指针,他的特点是支持拷贝,也支持移动。如果需要拷贝的场景就需要使用他了。底层是用引用计数的方式实现的。
- weak_ptr 是C++11设计出来的智能指针,他的名字翻译出来是弱指针,他完全不同于上面的智能指针,他不支持
RAII
,也就意味着不能用它直接管理资源,weak_ptr
的产生本质是要解决shared_ptr
的一个循环引用导致内存泄漏的问题。具体细节下面我们再细讲。- 智能指针析构时默认是进行
delete
释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。智能指针支持在构造时给一个删除器,所谓删除器本质就是一个可调用对象,这个可调用对象中实现你想要的释放资源的方式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调用删除器去释放资源。因为new[]
经常使用,所以为了简洁一点,unique_ptr
和shared_ptr
都特化了一份[]
的版本,使用时unique_ptr<Date[]> up1(new Date[5]);shared_ptr<Date[]> sp1(new Date[5]);
就可以管理new []
的资源。template <class T, class... Args> shared_ptr<T> make_shared (Args&&... args);
shared_ptr
除了支持用指向资源的指针构造,还支持make_shared
用初始化资源对象的值直接构造。shared_ptr
和unique_ptr
都支持了operator bool
的类型转换,如果智能指针对象是一个空对象没有管理资源,则返回false
,否则返回true
,意味着我们可以直接把智能指针对象给if
判断是否为空。shared_ptr
和unique_ptr
都得构造函数都使用explicit
修饰,防止普通指针隐式类型转换成智能指针对象。
代码1:
cpp
// 日期结构体,用于演示智能指针的行为
struct Date
{
int _year;
int _month;
int _day;
// 构造函数,提供默认参数
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
// 析构函数,用于观察对象销毁时机
~Date()
{
cout << "~Date()" << endl;
}
};
int main()
{
// 1. auto_ptr (C++17已废弃)
auto_ptr<Date> ap1(new Date);
// auto_ptr的拷贝构造会转移所有权
// ap1在拷贝后变为空指针,这是一个危险的特性
auto_ptr<Date> ap2(ap1);
// 危险:ap1已经为空,访问会导致未定义行为
//ap1->_year++; // 程序崩溃,但编译器不会提示错误
// 2. unique_ptr (推荐使用)
unique_ptr<Date> up1(new Date);
// unique_ptr禁止拷贝,体现独占所有权的语义
//unique_ptr<Date> up2(up1); // 编译错误
// unique_ptr支持移动语义
// 使用move显式转移所有权,up1在移动后变为空
unique_ptr<Date> up3(move(up1));
// 此时up1为空,up3获得对象的所有权
up1->_year++;// 编译错误!编译器会提示up1可能为空(编译会报错)
// 3. shared_ptr (推荐使用)
shared_ptr<Date> sp1(new Date);
// shared_ptr支持拷贝,通过引用计数实现共享所有权
shared_ptr<Date> sp2(sp1); // 引用计数+1
shared_ptr<Date> sp3(sp2); // 引用计数+1
// 显示当前引用计数
cout << sp1.use_count() << endl; // 输出3
// 所有指向同一对象的shared_ptr都可以正常访问对象
sp1->_year++;
cout << sp1->_year << endl; // 所有指针访问的是同一个对象
cout << sp2->_year << endl; // 值相同
cout << sp3->_year << endl; // 值相同
// shared_ptr也支持移动语义
// 移动后sp1变为空,但不影响sp2和sp3
shared_ptr<Date> sp4(move(sp1));
return 0;
// 离开作用域时,最后一个shared_ptr销毁时才会删除Date对象
}
代码2:
cpp
// 函数模板作为删除器,用于删除数组
template<class T>
void DeleteArrayFunc(T* ptr)
{
delete[] ptr;
}
// 仿函数类作为删除器,用于删除数组
template<class T>
class DeleteArray
{
public:
void operator()(T* ptr)
{
delete[] ptr;
}
};
// 文件指针的删除器
class Fclose
{
public:
void operator()(FILE* ptr)
{
cout << "fclose:" << ptr << endl;
fclose(ptr);
}
};
int main()
{
// 错误示范:直接使用new[]会导致析构错误
// unique_ptr<Date> up1(new Date[10]); // 错误:会用delete释放
// shared_ptr<Date> sp1(new Date[10]); // 错误:会用delete释放
// 方案1:使用数组特化版本
// C++标准库为数组提供了特化版本,自动使用delete[]
unique_ptr<Date[]> up1(new Date[5]);
shared_ptr<Date[]> sp1(new Date[5]);
// 方案2:使用自定义删除器
// 2.1 使用仿函数作为删除器
// unique_ptr要在模板参数中指定删除器类型
unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
// shared_ptr在构造函数中指定删除器对象
shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());
// 2.2 使用函数指针作为删除器
// unique_ptr需要在模板参数中指定函数指针类型
unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);
// shared_ptr直接传入函数指针
shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);
// 2.3 使用lambda表达式作为删除器
auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
// unique_ptr需要使用decltype推导lambda类型
unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);
// shared_ptr直接传入lambda
shared_ptr<Date> sp4(new Date[5], delArrOBJ);
// 管理其他资源:文件指针
// 使用仿函数关闭文件
shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());
// 使用lambda关闭文件
shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {
cout << "fclose:" << ptr << endl;
fclose(ptr);
});
return 0;
}
代码3:
cpp
int main()
{
// 1. 创建shared_ptr的几种方式
// 方式1:构造函数直接传递new的对象
shared_ptr<Date> sp1(new Date(2024, 9, 11));
// 方式2:使用make_shared函数(推荐)
// make_shared更安全且效率更高,因为只分配一次内存
shared_ptr<Date> sp2 = make_shared<Date>(2024, 9, 11);
// 方式3:使用auto进行类型推导
auto sp3 = make_shared<Date>(2024, 9, 11);
// 创建空的shared_ptr
shared_ptr<Date> sp4;
// 2. 智能指针的布尔转换
// 检查智能指针是否为空
// 实际上调用operator bool()
if (sp1)
cout << "sp1 is not nullptr" << endl;
// 检查智能指针是否为空
if (!sp4)
cout << "sp1 is nullptr" << endl;
// 3. 错误用法示例
// 错误:不能直接用=赋值原始指针
// 必须使用显式构造或make_shared
//shared_ptr<Date> sp5 = new Date(2024, 9, 11); // 编译错误
// 错误:同样不能直接赋值原始指针
//unique_ptr<Date> sp6 = new Date(2024, 9, 11); // 编译错误
// 正确的写法:
shared_ptr<Date> sp5(new Date(2024, 9, 11));
unique_ptr<Date> sp6(new Date(2024, 9, 11));
return 0;
}
代码4:
cpp
struct Node
{
A _val; // 节点存储的数据
// 方案1:普通指针
//Node* _next; // 指向下一个节点
//Node* _prev; // 指向前一个节点
// 缺点:需要手动管理内存,容易造成内存泄漏或重复释放
// 方案2:shared_ptr方式
//bit::shared_ptr<Node> _next; // 指向下一个节点的智能指针
//bit::shared_ptr<Node> _prev; // 指向前一个节点的智能指针
// 缺点:会导致循环引用,引用计数永不为0,无法释放内存
// 方案3:weak_ptr方式(最佳解决方案)
bit::weak_ptr<Node> _next; // 弱引用指向下一节点
bit::weak_ptr<Node> _prev; // 弱引用指向前一节点
// 优点:
// 1. weak_ptr不增加引用计数
// 2. 可以访问资源但不参与生命周期管理
// 3. 成功解决循环引用问题
};
int main()
{
// 方案1:使用普通指针
//Node* n1 = new Node;
//Node* n2 = new Node;
// 需要手动释放内存
//delete n1;
//delete n2;
// 方案2和3:使用智能指针
// 创建两个节点,初始引用计数均为1
bit::shared_ptr<Node> sp1(new Node);
bit::shared_ptr<Node> sp2(new Node);
// 打印初始引用计数
cout << sp1.use_count() << endl; // 输出1
cout << sp2.use_count() << endl; // 输出1
// 建立双向链接
sp1->_next = sp2; // 使用weak_ptr,不会增加sp2的引用计数
sp2->_prev = sp1; // 使用weak_ptr,不会增加sp1的引用计数
// 再次打印引用计数
cout << sp1.use_count() << endl; // 仍然是1
cout << sp2.use_count() << endl; // 仍然是1
// main函数结束时:
// 1. sp1和sp2离开作用域
// 2. 引用计数变为0
// 3. Node对象被正确释放
return 0;
}
这段代码演示了 shared_ptr
循环引用的问题,以及如何使用 weak_ptr
解决:
- 首先看结构体定义中三种不同的指针方式:
cpp
struct Node
{
A _val;
// 方式1:普通指针
// Node* _next;
// Node* _prev;
// 方式2:shared_ptr - 会导致循环引用
// bit::shared_ptr<Node> _next;
// bit::shared_ptr<Node> _prev;
// 方式3:weak_ptr - 解决循环引用
bit::weak_ptr<Node> _next;
bit::weak_ptr<Node> _prev;
};
- 使用普通指针的问题:
cpp
Node* n1 = new Node;
Node* n2 = new Node;
// ... 需要手动管理内存
delete n1;
delete n2;
- 需要手动管理内存
- 容易忘记释放导致内存泄漏
- 不够安全
- 使用 shared_ptr 时的循环引用问题:
cpp
bit::shared_ptr<Node> sp1(new Node); // sp1引用计数=1
bit::shared_ptr<Node> sp2(new Node); // sp2引用计数=1
cout << sp1.use_count() << endl; // 输出1
cout << sp2.use_count() << endl; // 输出1
// 创建循环引用
sp1->_next = sp2; // sp2的引用计数变为2
sp2->_prev = sp1; // sp1的引用计数变为2
cout << sp1.use_count() << endl; // 输出2
cout << sp2.use_count() << endl; // 输出2
问题:
- sp1引用了sp2,sp2引用了sp1
- 当main函数结束时,sp1和sp2的引用计数都不会降到0
- 导致内存泄漏
为什么引用计数会变为2:
cpp
// 初始状态:每个shared_ptr引用计数为1
bit::shared_ptr<Node> sp1(new Node); // count = 1
bit::shared_ptr<Node> sp2(new Node); // count = 1
/*
sp1 --> [Node1] count=1
sp2 --> [Node2] count=1
*/
// 当执行 sp1->_next = sp2 时:
// Node1的_next成员(它是shared_ptr)获得了一份sp2的拷贝
sp1->_next = sp2; // sp2的计数+1,变为2
/*
sp1 ----------------> [Node1] count=1
|
| _next
↓
sp2 ----------------> [Node2] count=2
*/
// 当执行 sp2->_prev = sp1 时:
// Node2的_prev成员(它是shared_ptr)获得了一份sp1的拷贝
sp2->_prev = sp1; // sp1的计数+1,变为2
/*
sp1 ----------------> [Node1] count=2 <---- _prev
| |
| _next |
↓ |
sp2 ----------------> [Node2] count=2 ------
*/
关键点:
- 每个Node结构体中的
_next
和_prev
是shared_ptr
- 当一个
shared_ptr
被赋值给另一个shared_ptr
时,会调用拷贝构造或赋值运算符 - 这会导致引用计数增加
所以:
sp1->_next = sp2
相当于又产生了一个指向Node2的shared_ptr
sp2->_prev = sp1
相当于又产生了一个指向Node1的shared_ptr
这就是为什么:
- Node1被sp1和Node2的_prev共同指向,引用计数为2
- Node2被sp2和Node1的_next共同指向,引用计数为2
这种情况下,即使main函数结束,sp1和sp2离开作用域:
- sp1的计数从2减为1(Node2的_prev还在引用它)
- sp2的计数从2减为1(Node1的_next还在引用它)
- 由于计数都不为0,两个Node对象都不会被释放
- 造成内存泄漏
这就是为什么需要使用 weak_ptr
- 它可以指向对象但不增加引用计数,从而打破这种循环引用。
- 使用 weak_ptr 解决循环引用:
cpp
bit::shared_ptr<Node> sp1(new Node);
bit::shared_ptr<Node> sp2(new Node);
sp1->_next = sp2; // weak_ptr不增加引用计数
sp2->_prev = sp1; // weak_ptr不增加引用计数
cout << sp1.use_count() << endl; // 仍然是1
cout << sp2.use_count() << endl; // 仍然是1
weak_ptr的优势:
- 不增加引用计数
- 可以访问资源
- 不参与资源的生命周期管理
- 当原始的shared_ptr被销毁时,weak_ptr会自动变为无效
总结:
weak_ptr
专门用来解决shared_ptr
循环引用的问题weak_ptr
不是 RAII 智能指针,它不管理资源的生命周期- 在需要打破循环引用的地方使用
weak_ptr
替代shared_ptr
四个智能指针:
四个智能指针:
- auto_ptr
- unique_ptr
- shared_ptr🌟
- weak_ptr
auto_ptr
cpp
template<class T>
class auto_ptr
{
public:
// RAII
// 像指针一样
auto_ptr(T* ptr)
:_ptr(ptr)
{}
~auto_ptr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// ap3(ap1)
// 管理权转移
auto_ptr(auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
private:
T* _ptr;
};
unique_ptr
cpp
template<class T>
class unique_ptr
{
public:
// RAII
// 像指针一样
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// ap3(ap1)
// 管理权转移
// 防拷贝
unique_ptr(unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
private:
T* _ptr;
};
shared_ptr(重要)
cpp
template<class T>
class shared_ptr
{
public:
// 构造函数:创建新的shared_ptr对象
// ptr: 指向要管理的对象的指针
// 初始化:设置指针和引用计数(初始为1)
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pcount(new int(1)) // 新建计数器,初始值为1
{}
// 析构函数:当shared_ptr对象销毁时调用
// 减少引用计数,当计数为0时释放资源
~shared_ptr()
{
if (--(*_pcount) == 0) // 引用计数减1,如果变为0
{
cout << "delete:" << _ptr << endl;
delete _ptr; // 释放管理的对象
delete _pcount; // 释放计数器
}
}
// 重载*运算符:访问管理的对象
T& operator*()
{
return *_ptr;
}
// 重载->运算符:访问管理的对象的成员
T* operator->()
{
return _ptr;
}
// 拷贝构造函数:创建一个新的shared_ptr,共享同一个对象
// sp: 现有的shared_ptr对象
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr) // 共享被管理的对象
, _pcount(sp._pcount) // 共享引用计数
{
++(*_pcount); // 增加引用计数
}
// 赋值运算符:将一个shared_ptr赋值给另一个
// 需要处理自赋值和原有资源的释放
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
// 自赋值检查
if (_ptr == sp._ptr)
return *this;
// 释放原有资源(如果引用计数变为0)
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
// 共享新资源
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount); // 增加新资源的引用计数
return *this;
}
// 获取当前引用计数
int use_count() const
{
return *_pcount;
}
// 获取原始指针
T* get() const
{
return _ptr;
}
private:
T* _ptr; // 指向管理的对象的指针
int* _pcount; // 指向引用计数的指针
// 不能用static int _pcount的原因:
// 1. static成员是类共享的,所有shared_ptr实例会共享同一个计数
// 2. 无法区分不同对象组的引用计数
// 3. 不能正确管理多个不同对象的生命周期
};
这里不能用
static int _pcount;
而是用int* _pcount;
这涉及到shared_ptr
的核心设计:
cpp// 错误方式:使用静态计数器 template<class T> class SharedPtr { private: T* _ptr; static int _pcount; // 静态成员,所有SharedPtr对象共享同一个计数器 }; int main() { // 问题演示 SharedPtr<Date> sp1(new Date(2024, 2, 14)); // _pcount = 1 SharedPtr<Date> sp2(new Date(2025, 1, 1)); // _pcount = 2 // 问题:sp1和sp2共享同一个静态计数器! // 无法区分不同对象的引用计数 }
正确方式:每组共享指针有自己的计数器:
cpptemplate<class T> class SharedPtr { private: T* _ptr; // 指向实际对象的指针 int* _pcount; // 指向引用计数的指针 public: SharedPtr(T* ptr = nullptr) : _ptr(ptr) , _pcount(new int(1)) // 每个新对象组都有自己的计数器 {} }; int main() { // 正确示例 SharedPtr<Date> sp1(new Date(2024, 2, 14)); // _pcount指向值为1的计数器 SharedPtr<Date> sp2(sp1); // 共享sp1的计数器,值增加到2 SharedPtr<Date> sp3(new Date(2025, 1, 1)); // 新的计数器,值为1 SharedPtr<Date> sp4(sp3); // 共享sp3的计数器,值为2 // sp1和sp2共享一个计数器 // sp3和sp4共享另一个计数器 }
使用静态计数器的问题:
- 所有
SharedPtr
对象会共享同一个计数器- 无法区分不同对象组的引用计数
- 不能正确管理多个不同对象的生命周期
使用成员指针计数器的优势:
- 每组共享同一对象的指针有自己的计数器
- 可以准确跟踪每个对象被引用的次数
- 当计数器归零时,才释放对应的对象
这就是为什么要将计数器设计为指针成员而不是静态成员的原因。
weak_ptr
cpp
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
4. 智能指针的原理
- 下面我们模拟实现了auto_ptr和unique_ptr的核心功能,这两个智能指针的实现比较简单,大家了解一下原理即可。auto_ptr的思路是拷贝时转移资源管理权给被拷贝对象,这种思路是不被认可的,也不建议使用。unique_ptr的思路是不支持拷贝。
- 大家重点要看看shared_ptr是如何设计的,尤其是引用计数的设计,主要这里一份资源就需要一个引用计数,所以引用计数才用静态成员的方式是无法实现的,要使用堆上动态开辟的方式,构造智能指针对象时来一份资源,就要new一个引用计数出来。多个shared_ptr指向资源时就++引用计数,shared_ptr对象析构时就--引用计数,引用计数减到0时代表当前析构的shared_ptr是最后一个管理资源的对象,则析构资源。

cpp
namespace bit
{
// auto_ptr实现 - C++17已废弃的设计
template<class T>
class auto_ptr
{
public:
// 构造函数,接管原始指针
auto_ptr(T* ptr)
:_ptr(ptr)
{}
// 拷贝构造函数 - 转移所有权
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr; // 源对象置空
}
// 赋值运算符 - 转移所有权
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)
{
delete _ptr; // 释放当前资源
_ptr = ap._ptr; // 获取ap的资源
ap._ptr = nullptr; // ap置空
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 指针操作符重载
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};
// unique_ptr实现 - 独占所有权
template<class T>
class unique_ptr
{
public:
// explicit防止隐式转换
explicit unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
// 禁止拷贝和赋值
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
// 支持移动语义
unique_ptr(unique_ptr<T>&& sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
unique_ptr<T>& operator=(unique_ptr<T>&& sp)
{
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
return *this;
}
private:
T* _ptr;
};
// shared_ptr实现 - 共享所有权
template<class T>
class shared_ptr
{
public:
// 构造函数
explicit shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
, _pcount(new int(1))
{}
// 支持自定义删除器的构造
template<class D>
shared_ptr(T* ptr, D del)
: _ptr(ptr)
, _pcount(new int(1))
, _del(del)
{}
// 拷贝构造 - 增加引用计数
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
, _del(sp._del)
{
++(*_pcount);
}
// 释放资源
void release()
{
if (--(*_pcount) == 0)
{
_del(_ptr); // 使用删除器释放资源
delete _pcount;
_ptr = nullptr;
_pcount = nullptr;
}
}
// 赋值运算符
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
release();
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
_del = sp._del;
}
return *this;
}
~shared_ptr()
{
release();
}
// 访问接口
T* get() const { return _ptr; }
int use_count() const { return *_pcount; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr; // 管理的资源指针
int* _pcount; // 引用计数
// 在实际实现中应使用原子计数
//atomic<int>* _pcount;
// 默认删除器
function<void(T*)> _del = [](T* ptr) {delete ptr; };
};
// weak_ptr实现 - 弱引用
template<class T>
class weak_ptr
{
public:
weak_ptr() {}
// 通过shared_ptr构造
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
// 赋值运算符
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
private:
T* _ptr = nullptr;
};
}
int main()
{
// auto_ptr测试 - 展示所有权转移的问题
bit::auto_ptr<Date> ap1(new Date);
bit::auto_ptr<Date> ap2(ap1); // ap1变为空
//ap1->_year++; // 错误:ap1已经为空
// unique_ptr测试 - 展示独占所有权
bit::unique_ptr<Date> up1(new Date);
//bit::unique_ptr<Date> up2(up1); // 错误:不能拷贝
bit::unique_ptr<Date> up3(move(up1)); // 正确:可以移动
// shared_ptr测试 - 展示共享所有权
bit::shared_ptr<Date> sp1(new Date);
bit::shared_ptr<Date> sp2(sp1); // 引用计数增加
bit::shared_ptr<Date> sp3(sp2); // 引用计数增加
cout << sp1.use_count() << endl; // 输出引用计数
sp1->_year++; // 修改共享对象
cout << sp1->_year << endl; // 所有指针看到相同的值
cout << sp2->_year << endl;
cout << sp3->_year << endl;
return 0;
}