【C++ 智能指针全解析】从内存泄漏痛点到 RAII + unique/shared/weak_ptr 手撕实现
作者 :yuuki233233
时间: 2026年3月6日
目标:德国CS本科 + 特斯拉软件工程师代码仓库:https://github.com/yuuki233233/cpp-learning-journey
CSDN 主页:https://blog.csdn.net/yuuki233233
欢迎点赞、收藏、评论,一起卷现代 C++!
智能指针的使用及原理
智能指针使用场景分析
问题引入:
case 1:无异常
try中创建array1、array2,array1,array2都创建成功,没抛出异常,array正常销毁。(无内存泄漏)
case 2:有异常try中创建array1、array2,array1先发生异常,因创建失败而没申请空间,因先发生异常而没创建array2。(无内存泄漏)try中创建array1、array2,array2发生异常,此时array1申请了空间,因 array2 发生异常需 被 catch 捕捉,这里要另外写一套捕获释放逻辑,很麻烦
因为 case 2 中可能会因异常而导致内存泄漏,需要我们手动再写一遍 catch 捕捉,就此产生了智能指针帮我们管理内存。
cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
double Divide(int a, int b)
{
if (b == 0)
{
throw "Divide by zero condition!";
}
else
{
return (double)a / (double)b;
}
}
void Func()
{
// 如果发生除0错误抛出异常,另外下面的 array1 和 array2 没有得到释放
// 捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再重新抛出去
// 但如果 array2 抛异常,就还需要写异常捕获释放逻辑,这里更好的办法是智能指针
int* array1 = new int[10];
int* array2 = new int[10];
try
{
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
catch (...) // 需捕捉异常,里面要 delete,外层也要 delete,这很麻烦
{
cout << "delete []" << array1 << endl;
cout << "delete []" << array2 << endl;
delete[] array1;
delete[] array2;
throw;
}
// 外层 delete
cout << "delete []" << array1 << endl;
delete[] array1;
cout << "delete []" << array2 << endl;
delete[] array2;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown" << endl;
}
return 0;
}
RAII:资源获取即初始化(Resource Acquisition Is Initialization)
在上面的例子中,我们发现手动 new/delete 最大的问题在于:
- 有可能因为程序的逻辑原因,没有执行到释放,比如前面
new了,后面delete了,中间就可能抛异常 - 异常发生时,
delete[] array1和delete[] array2可能得不到执行 → 内存泄漏。 - 代码需要在
try和catch里都写释放逻辑 → 重复、容易出错。 - 连续的抛异常可能会让我们写
new和malloc非常的难受,比如函数再复杂一点(多层嵌套、多个资源),手动管理几乎不可能不出错。
所以引入智能指针来解决上述的问题:C++ 的解决方案就是 RAII(资源获取即初始化)
总结:获取到资源就别自己主动去释放,很容易出现问题。这时候就交给一个对象去管理,这个对象把指针给存起来,出了作用域就一定会调用析构函数,无论是抛异常还是正常结束,总会去释放的
RAII 的核心思想(一句话记住)
资源(内存、文件、锁、socket 等)的生命周期 与对象的生命周期绑定:
- 对象构造时获取资源(new、open、lock)
- 对象析构时自动释放资源(delete、close、unlock)
这样即使抛出异常,栈展开也会自动调用对象的析构函数,资源不会泄漏。
cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
// 智能指针
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{ }
~SmartPtr()
{
cout << "delete[]" << _ptr << endl;
delete[] _ptr;
}
private:
T* _ptr;
};
double Divide(int a, int b)
{
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];
SmartPtr<int> sp3 = new int[10];
SmartPtr<int> sp4 = new int[10];
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
int main()
{
try
{
Func();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
cout << "Unkown" << endl;
}
return 0;
}
如果是自定义类型或 pair 类型 ,这时就需要重载 operator->()、operator*()、operator[]
cpp
// 智能指针
template<class T>
class SmartPtr
{
public:
// RAII
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;
};
void Func()
{
// 智能指针:自动初始化、自动析构,不需重复捕获异常
SmartPtr<int> sp1 = new int[10];
SmartPtr<pair<int, int>> sp5 = new pair<int, int>[10];
sp1[5] = 50; // operator[]
sp5->first = 1; // operator->()
sp5->second = 2; // operator->()
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
C++标准库智能指针
auto_ptr是 C++98 设计出来的智能指针,特点:拷贝时把对象资源转移给拷贝对象,被拷贝对象为空(强烈不建议使用)- C++11 提供了三种智能指针(在
memory头文件里),它们都是 RAII 的典型应用,分别是unique_ptr、shared_ptr、weak_ptr
unique_ptr 的使用和原理以及模拟实现
unique_ptr 使用
unique_ptr:不支持拷贝,只支持移动 (不需要拷贝的场景)
cpp
unique_ptr<Date> up1(new Date);
// 不支持拷贝构造,支持移动构造
//unique_ptr<Date> up2(up1);
unique_ptr<Date> up3(move(up1));
unique_ptr 原理
auto_ptr的思路是拷贝是转移资源管理权给被拷贝对象,unique_ptr的思路是不支持拷贝
unique_ptr 模拟实现
cpp
// 模拟实现 unique_ptr
namespace yuuki
{
template<class T>
class unique_ptr
{
public:
// 默认构造
unique_ptr(T* ptr)
:_ptr(ptr)
{ }
~unique_ptr()
{
if (_ptr)
{
cout << "delete[]:" << _ptr << endl;
delete _ptr;
}
}
// 支持 自定义类型 和 pair类型
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// unique_ptr 不支持左值,需 delete
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
// unique_ptr 支持右值
// 为什么移动构造没有 delete[] _ptr,因为 sp4 是新建的,_ptr 没有申请内存
unique_ptr(unique_ptr<T>&& sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
unique_ptr<T>& operator=(unique_ptr<T>&& sp)
{
// sp1 = sp3,sp1 中的 _ptr 存在数据,需要 delete
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
}
private:
T* _ptr;
};
}
shared_ptr 的使用和原理以及模拟实现
shared_ptr 使用
shared_ptr:支持拷贝和移动,底层是用引用计数的方式实现的。(需要拷贝的场景)
cpp
shared_ptr<Date> sp1(new Date);
// 支持拷贝构造和移动构造
shared_ptr<Date> sp2(sp1);
shared_ptr<Date> sp3(sp2);
// sp3 先析构, --计数 -> sp2
// sp2 再析构, --计数 -> sp1
// sp1 析构, --计数 -> 0,完成整个析构
shared_ptr 原理
shared_ptr:- 共享:单个对象不共享计数 ,如默认构造 sp1 和 默认构造 sp3;相同对象的拷贝构造对象共用同个计数,如 默认构造 sp1 拷贝构造 sp2(sp1)。
count引用计数:- 常量(每一个对象有不同的引用计数):默认构造
sp1 -> int = 1,拷贝构造sp2(sp1) -> int = 2,两个 int 不共享,但因为 sp2 是拷贝构造,第一个 int++,最终sp1 -> int = 2。sp2 析构int = 2 -> int = 1,sp1 析构int = 2 -> int = 1。这两个对象都不共享同一个 int - 静态(每一个对象有共同的引用计数):静态属于这个类的所有对象 。上面的情况不变,额外添加一个默认构造 sp3,
原本 sp3 -> int = 1,但因为用的是静态成员变量,sp3 共享 sp1 和 sp2 的 int,结果 sp3 -> int = 3 - 指针:**动态开辟,来一份资源 new 一个计数,两个对象同时指向一份资源,如:sp1 和 sp2(sp1)
- 常量(每一个对象有不同的引用计数):默认构造
shared_ptr 模拟实现
cpp
#include<iostream>
#include<memory>
using namespace std;
class Date
{
public:
Date(int year = 2026, int month = 3, int day = 4)
:_year(year)
,_month(month)
,_day(day)
{ }
Date(const Date& d)
:_year(d._year)
,_month(d._month)
,_day(d._day)
{ }
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
~Date()
{
cout << "~Date()" << endl;
}
//private:
int _year = 1;
int _month = 1;
int _day = 1;
};
namespace yuuki
{
template<class T>
class shared_ptr
{
public:
// sp1 默认构造生成 _pcount 计数为 1
shared_ptr(T* ptr)
:_ptr(ptr)
,_pcount(new int(1))
{ }
// sp2(sp1) _pcount 计数需++
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
{
(*_pcount)++;
}
//// sp1 = sp4 sp5 = sp4 sp1 = sp2
//shared_ptr<T>& operator=(const shared_ptr<T>& sp)
//{
// if (_ptr != sp._ptr)
// {
// if (_ptr)
// {
// delete _ptr;
// delete _pcount;
// }
// _ptr = sp._ptr;
// _pcount = sp._pcount;
// (*_pcount)++;
// }
// else
// {
// return *this;
// }
//
// return *this;
//}
// sp1 = sp4 sp5 = sp4 sp1 = sp2
shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
if (this != &sp) { // 防止自赋值
if (_ptr != sp._ptr) { // 防止指向同一块内存(可选,但好习惯)
if (--(*_pcount) == 0) { // 计数减到0才释放
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount); // 新增引用计数
}
}
return *this;
}
// 每次析构 sp2 _pcount 计数需--
~shared_ptr()
{
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// 修正 shared_ptr 中 use_count 的声明
int use_count()
{
return (*_pcount);
}
private:
T* _ptr;
int* _pcount;
};
}
int main()
{
yuuki::shared_ptr<Date> sp1(new Date);
yuuki::shared_ptr<Date> sp2(sp1);
// 调用拷贝构造,因为 sp3 不存在
yuuki::shared_ptr<Date> sp3 = sp2;
cout << "sp1.use_count():" << sp1.use_count() << endl; // 3
yuuki::shared_ptr<Date> sp4(new Date);
cout << "sp4.use_count():" << sp4.use_count() << endl; // 1
sp1->_year++;
cout << "sp1->_year:" << sp1->_year << endl; // 2027
cout << "sp2->_year:" << sp2->_year << endl; // 2027
cout << "sp3->_year:" << sp3->_year << endl; // 2027
cout << "sp4->_year:" << sp4->_year << endl; // 2026
/*
* 赋值构造
* 1. 对象不存在,拷贝构造
* 2. 对象存在,赋值构造
*/
// 自己给自己赋值
sp1 = sp2;
cout << "sp1.use_count():" << sp1.use_count() << endl; // 3
// 对象不存在,拷贝构造
yuuki::shared_ptr<Date> sp5 = sp4;
cout << "sp5.use_count():" << sp5.use_count() << endl; // 2
// 对象存在,赋值构造
sp4 = sp1;
cout << "sp1.use_count():" << sp1.use_count() << endl; // 4
// 对象存在,赋值构造
yuuki::shared_ptr<Date> sp6(new Date);
sp1 = sp6;
cout << "sp1.use_count():" << sp1.use_count() << endl; // 2
return 0;
}
shared_ptr 循环引用问题和 weak_ptr 使用原理
shared_ptr大多数情况下管理资源非常合理,支持RALL,也支持拷贝。但在循环引用得场景下会导致资源内存泄漏weak_ptr:不支持RALL,本质是解决shared_ptr的循环引用导致内存泄漏
shared_ptr 循环引用问题
就拿双向链表来解释怎么出现 循环引用 的问题:
- 我们所知,双向链表两个节点的 next 和 prev 互相指向,如果要析构是两个对象遍历链表析构(用两个对象是防止找不到节点而导致访问野指针)
- 前提提醒:整个节点都是由
shared_ptr构成 - 到了
shared_ptr,右边节点由_next管理,左边节点由_prev管理 - 左节点 释放,
_next析构,因_next析构,指向的右节点 - 右节点 释放,
_prve析构,因_prve析构,指向左节点
- 逻辑上形成闭环,因为谁都不会释放,导致内存泄漏
- 把 ListNode 结构体中的
_next 和 _prve从shared_ptr改成weak_ptr,这是就不会增加引用计数,_next 和 _prev不参与资源释放管理逻辑,就打破了循环应用
weak_ptr(弱引用)
- 不增加引用计数,用于打破 shared_ptr 的循环引用导致的内存泄漏
- 不能直接解引用,必须用
lock()转为 shared_ptr 使用
cpp
#include<iostream>
using namespace std;
template<class T>
struct ListNode
{
T _data;
/*shared_ptr<ListNode> _next;
shared_ptr<ListNode> _prev;*/ // 循环引用!a 和 b 永远不会析构
// 这里改成 weak_ptr,当 n1->_next = n2,绑定 shared_ptr 时
// 不增加 n2 的引用计数,不参与资源释放的管理,就不会形成循环引用
weak_ptr<ListNode> _next; // 弱引用,不增计数
weak_ptr<ListNode> _prve;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
shared_ptr<ListNode<int>> n1(new ListNode<int>);
shared_ptr<ListNode<int>> n2(new ListNode<int>);
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
n1->_next = n2;
n2->_prve = n1;
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
return 0;
}
weak_ptr 应用
weak_ptr不支持RALL,也不支持访问资源,只支持绑定到shared_ptr,不增加shared_ptr的引用计数,就可以解决上述的循环引用问题expired关键字:检查指向资源是否过期use_count关键字:获取shared_ptr的引用计数lock关键字:返回一个管理资源的shared_ptr,如果资源已经释放,返回一个空对象,资源没被释放,则返回的资源是安全的
cpp
#include<iostream>
using namespace std;
#include<string>
int main()
{
shared_ptr<string> p1(new string("1"));
shared_ptr<string> p2(p1);
cout << "*p1 = " << *p1 << endl;
cout << "*p2 = " << *p2 << endl;
weak_ptr<string> wp = p1;
// expired 判断是否释放资源,false 表示没释放, true 表示已释放
cout << wp.expired() << endl; // 0:没释放
cout << wp.use_count() << endl; // 2
p1 = make_shared<string>("2");
cout << "*p1 = " << *p1 << endl;
cout << wp.expired() << endl; // 0:没释放
cout << wp.use_count() << endl; // 1
p2 = make_shared<string>("3");
cout << "*p2 = " << *p2 << endl;
cout << wp.expired() << endl; // 1:释放
cout << wp.use_count() << endl; // 0
// p2已被改动,所以计数为1
wp = p1;
cout << wp.expired() << endl; // 0:没释放
cout << wp.use_count() << endl; // 1
shared_ptr<string> p3 = wp.lock();
cout << wp.expired() << endl; // 0
cout << wp.use_count() << endl; // 2
// lock 返回一个 shared_ptr,自己生成一个 shared_ptr,保证这份不被制空
// 可以用 shared_ptr 或 auto 接受类型
// lock:在资源没释放之前,里面再产生一个 shared_ptr 管理这份资源
*p3 += "$$$";
cout << "*p1 = " << *p1 << endl;
cout << "*p2 = " << *p2 << endl;
cout << "*p3 = " << *p3 << endl;
return 0;
}
unique_ptr 与 shared_ptr 删除器使用及模拟实现
删除器 使用(为什么设置删除器)
- 智能指针析构默认进行 delete 释放资源,如果不是 new 出来的资源,交给智能指针管理析构就会崩溃(如:
shared_ptr<Date> sp1(new Date[10]),new[] 了数组,new[] 和 delete 错位了)。为此防止这情况,智能指针支持再构造时给一个删除器(自己定义的可调用对象,其用来释放资源)。因为经常使用 new[],所以C++11把 unique_ptr 和 shared_ptr 都特化了份 [] 的版本,使用时unique_ptr<Date[]> up1(new Date[5]); shared_ptr<Date[]> sp1(new Date[5]);就可以方便管理 new[] 的资源
cpp
#include<iostream>
#include<memory>
using namespace std;
class Date
{
public:
Date(int year = 2026, int month = 3, int day = 4)
:_year(year)
,_month(month)
,_day(day)
{ }
~Date()
{
cout << "~Date()" << endl;
}
//private:
int _year = 1;
int _month = 1;
int _day = 1;
};
// 仿函数形式
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()
{
// 错误写法
//unique_ptr<Date> up1(new Date[5]);
//shared_ptr<Date> sp1(new Date[5]);
/* 解决方法1 */
// 日常经常使用 new[],使用 unique_ptr 和 shared_ptr
// 实现了哥特化版本,这个版本析构时用 delete[]
unique_ptr<Date[]> up1(new Date[5]);
shared_ptr<Date[]> sp1(new Date[5]);
/* 解决方法2 */
// 用定值删除器时,unique_ptr 和 shared_ptr 又不一样
/*
* unique_ptr 在类声明里传
* template <class T, class D = default_delete<T>> class unique_ptr;
*
* shared_ptr 在构造函数里面传
* template <class U, class D> shared_ptr (U* p, D del);
*
*/
// 仿函数对象做删除器
unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());
// 函数指针做删除器
unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);
shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>); // 仿函数需实例化
// lambda表达式做删除器
auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);
shared_ptr<Date> sp4(new Date[5], [](Date* ptr) {delete[] ptr; }); // lambda 表达式做删除器
// 实现其他资源管理器
shared_ptr<FILE> sp5(fopen("test.cpp", "r"), Fclose());
shared_ptr<FILE> sp6(fopen("test.cpp", "r"), [](FILE* ptr) {
delete[] ptr;
fclose(ptr);
}); // 在函数参数传,实参传给形参通过模板自动推导 lambda 的类型
// lambda、函数指针、仿函数都很好用
unique_ptr<FILE, Fclose> up5(fopen("test.cpp", "r"));
// 而 unique_ptr 必须传类型,但 lambda 拿不到类型,就很难用 lambda 写 unique_ptr
// 这时候就可以用 decltype 关键字来推导类型
auto fcloseFunc = [](FILE* ptr) {fclose(ptr); };
unique_ptr<FILE, decltype(fcloseFunc)> up6(fopen("test.cpp", "r"), fcloseFunc);
/* 总结
* 如果用 unique_ptr 定制删除器,建议用仿函数
* 如果用 shared_ptr 定制删除器,都可以,lambda 更好
*/
return 0;
}
删除器 模拟实现
cpp
#include<iostream>
#include<memory>
using namespace std;
class Date
{
public:
Date(int year = 2026, int month = 3, int day = 4)
:_year(year)
,_month(month)
,_day(day)
{ }
~Date()
{
cout << "~Date()" << endl;
}
//private:
int _year = 1;
int _month = 1;
int _day = 1;
};
#include<functional>
namespace yuuki
{
// 因为类里面访问不了构造函数的模板参数
// 所以 unique_ptr 实现成 template<class T, class D>
// 用删除器时必须传类型,用 lambda 时就特别不方便
template<class T>
class shared_ptr
{
public:
//-----------------------------------------------------------------------
// 版本1:支持单参数
// sp1 默认构造生成 _pcount 计数为 1(有1个模板参数)
shared_ptr(T* ptr)
:_ptr(ptr)
,_pcount(new int(1))
{ } // 这里可能会因为没有调用 del 而报错,所以下面写了 lambda 作为缺省值去默认构造 del
// shared_ptr 删除器写法(有2个模板参数)
template<class D> // D = delete
shared_ptr(T* ptr, D del) // 包装器:del 传到默认构造去,D 可能是 lambda、仿函数、函数指针
:_ptr(ptr)
,_pcount(new int(1))
,_del(del) // 难点:需要把删除器保存下来(因为D是构造函数的模板参数,所以定义不了)
{ }
// 版本2:支持双参数
// sp2(sp1) _pcount 计数需++
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 (this != &sp) { // 防止自赋值
if (_ptr != sp._ptr) { // 防止指向同一块内存(可选,但好习惯)
if (--(*_pcount) == 0) { // 计数减到0才释放
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount); // 新增引用计数
}
}
return *this;
}
// 每次析构 sp2 _pcount 计数需--
~shared_ptr()
{
if (--(*_pcount) == 0)
{
//delete _ptr;
_del(_ptr); // 传的是删除器,释放时用删除器释放
delete _pcount;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
// 修正 shared_ptr 中 use_count 的声明
int use_count()
{
return (*_pcount);
}
private:
T* _ptr;
int* _pcount;
//D _del; // 类里面用不了函数模板的参数
// 包装器:del 传到默认构造去,D 可能是 lambda、仿函数、函数指针
function<void(T*)> _del = [](T* ptr) { delete ptr; };
// 传 lambda 缺省值,lambda 默认是 delete
// 坑:delete[],会导致程序死循环
};
}
int main()
{
yuuki::shared_ptr<Date> sp1(new Date); // 这里会因没有调用到 del 而程序崩溃
// 定制删除器
yuuki::shared_ptr<Date> sp2(new Date[10], [](Date* ptr) {delete[] ptr; });
return 0;
}
make_shared 使用及好处
cpp
#include<iostream>
#include<memory>
using namespace std;
class Date
{
public:
Date(int year = 2026, int month = 3, int day = 4)
:_year(year)
,_month(month)
,_day(day)
{ }
~Date()
{
cout << "~Date()" << endl;
}
//private:
int _year = 1;
int _month = 1;
int _day = 1;
};
int main()
{
shared_ptr<Date> sp1(new Date(2026, 3, 4));
// 先传日期类的参数,new 了个日期类对象交给 make_shared 管理
// shared_ptr 每一个对象都会有引用计数,每一个引用计数都会向堆申请内存
// 向堆申请大量的开小块内存,会有性能的消耗,碎片化的内存也会导致内存碎片的问题
// 这时候就要用到内存池进行管理小块内存
// make_shared 会在 Date 内存中多开 4bite 存引用计数
// 这样只用开一次内存,make_shared 会高效一点点
shared_ptr<Date> sp2 = make_shared<Date>(2026, 3, 4);
return 0;
}
unique_ptr 和 shared_ptr 转为 bool
shared_ptr 和 unique_ptr 都支持 operator bool 的类型转换,如果智能指针对象是一个空对象没有管理资源,则返回 false,否则返回 true,我们可以直接把只能智能指针对象用 if 判断是否为空
cpp
#include<iostream>
#include<memory>
using namespace std;
int main()
{
shared_ptr<Date> sp1(new Date(2026, 3, 4));
shared_ptr<Date> sp2 = make_shared<Date>(2026, 3, 4);
shared_ptr<Date> sp4;
if (sp1)
cout << "sp1 is not nullptr" << endl;
if (sp2.operator bool()) // 原始版本为 operator bool
cout << "sp2 if not nullptr" << endl;
if (sp4)
cout << "sp4 is not nullptr" << endl;
// 报错:禁止使用隐式类型转换 + 拷贝构造
//shared_ptr<Date> sp5 = new Date(2026, 3, 4);
shared_ptr<Date> sp6(new Date(2026, 3, 4)); // 正确用法
return 0;
}
总结
智能指针是现代 C++ 的核心武器:
- unique_ptr:独占、零开销、noexcept 友好
- shared_ptr:共享、引用计数
- weak_ptr:防循环引用
用 make_unique / make_shared 写代码更安全、更高效。
下一站:Linux 命令复习 + 网络编程入门!
代码仓库:https://github.com/yuuki233233/cpp-learning-journey
CSDN 主页:https://blog.csdn.net/yuuki233233
欢迎评论交流,一起卷现代 C++!