C++ 智能指针详解:从入门到实践
日常分享整合的学习笔记:
适合初学者阅读
版本:C++11 及以上版本
内容包含:
auto_ptr(了解即可)、unique_ptr、shared_ptr、weak_ptr、原理解析、使用场景、常见坑点。
一、为什么需要智能指针?
在 C++ 中,动态内存通常需要我们手动管理。
例如:
int* p = new int(10);
使用完成后:
delete p;
如果忘记 delete:
void test()
{
int* p = new int(10);
}
那么这块内存就永远无法释放。
这就叫:
内存泄漏(Memory Leak)
程序运行时间越久:
-
内存占用越来越高
-
系统越来越卡
-
最终可能崩溃
尤其在:
-
服务器
-
网络编程
-
游戏引擎
-
数据库
中非常危险。
二、什么是智能指针?
智能指针本质上:
是一个类
它内部封装了普通指针。
核心思想:
RAII(资源获取即初始化)
即:
-
对象创建时获取资源
-
对象销毁时自动释放资源
这样就不用手动 delete 了。
例如:
{
std::unique_ptr<int> p(new int(10));
}
代码块结束后:
p
会自动析构。
析构函数内部自动:
delete
因此不会内存泄漏。
三、C++ 中的智能指针种类
C++11 后主要有三种:
| 智能指针 | 作用 |
|---|---|
| unique_ptr | 独占所有权 |
| shared_ptr | 共享所有权 |
| weak_ptr | 解决 shared_ptr 循环引用 |
此外还有:
auto_ptr
但已经被废弃。
了解即可。
四、auto_ptr(已废弃)
早期 C++ 提供的智能指针。
例如:
std::auto_ptr<int> p1(new int(10));
问题在于:
std::auto_ptr<int> p2 = p1;
此时:
-
p2 获得资源
-
p1 变为空
也就是说:
所有权会偷偷转移
非常危险。
因此 C++11 后废弃。
不要再使用。
五、unique_ptr(重点)
1、什么是 unique_ptr?
std::unique_ptr
表示:
独占资源
同一时间:
-
只能有一个 unique_ptr 管理资源
类似:
“这个对象只属于我”
2、基本使用
#include <iostream>
#include <memory>
using namespace std;
//示例1:
int main()
{
unique_ptr<int> p(new int(100)); // 分配内存,构造 unique_ptr,其作用域范围为最近的{}
cout << *p << endl; //输出 100
return 0;
}// ← 离开作用域,p 析构,自动 delete 内存
------------------------------------------------------------------------------
//示例2:
int main()
{
{
unique_ptr<int> p(new int(100)); // 分配内存
cout << *p << endl; // 使用
} // ← 离开作用域,p 析构,自动 delete 内存
return 0;
}
输出:
100
程序结束:
自动释放内存。
3、为什么不能拷贝?
错误代码:
unique_ptr<int> p1(new int(10));
unique_ptr<int> p2 = p1;
会直接报错。
因为:
unique_ptr 不允许多个对象同时管理同一块内存
否则会出现:
delete 两次
程序崩溃。
4、移动语义( move( ) )
unique_ptr 支持:
move 转移所有权
例如:
unique_ptr<int> p1(new int(10));
unique_ptr<int> p2 = move(p1);
此时:
p1 == nullptr
资源已经转移给:
p2
5、推荐使用 make_unique
不推荐:
unique_ptr<int> p(new int(10));
推荐:
auto p = make_unique<int>(10); //使用auto自动推导
优点:
-
更安全
-
更简洁
-
避免异常问题
6、管理数组
unique_ptr<int[]> p(new int[5]);
使用:
p[0] = 100;
注意:
数组版本内部会自动调用:
delete[]
六、shared_ptr(重点)
1、什么是 shared_ptr?
shared_ptr
表示:
多个智能指针共享同一资源
例如:
auto p1 = make_shared<int>(10);//make_shared<int>(10) 在堆上分配一块内存,存储整数 10
auto p2 = p1; //拷贝构造,不会分配新内存,只是增加引用计数,auto 自动推导为 shared_ptr<int>
此时:
-
p1 管理资源
-
p2 也管理资源
资源不会立刻释放。
只有:
所有 shared_ptr 都销毁
资源才会释放。
2、引用计数
shared_ptr 内部维护:
引用计数
例如:
auto p1 = make_shared<int>(10);
计数:
1
再拷贝:
auto p2 = p1;
计数变成:
2
再拷贝:
auto p3 = p1;
计数:
3
可以查看:
cout << p1.use_count() << endl; //输出3
//注:
//use_count() 是 C++ 中 shared_ptr 的一个成员函数,
//用于返回当前与 shared_ptr 共享同一块内存的所有智能指针的数量(即引用计数)
3、示例代码
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> p1 = make_shared<int>(100); //像上面一样使用自动推导auto p1也行
cout << p1.use_count() << endl; //输出1
shared_ptr<int> p2 = p1; //也可以使用auto
cout << p1.use_count() << endl; //输出2
cout << p2.use_count() << endl; //输出2
return 0;
}
输出:
1
2
2
4、shared_ptr 的内部原理
shared_ptr 内部大概包含:
class SharedPtr
{
private:
int* ptr;
int* count;
};
其中:
count
记录引用数量。
每次拷贝:
(*count)++;
析构时:
(*count)--;
当计数为 0:
delete ptr;
5、不要这样写
错误示例:
int* p = new int(10);
//直接使用裸指针构造
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);
问题:
两个 shared_ptr 会认为自己是第一次管理资源
最终:
delete 两次
正确做法:
auto p1 = make_shared<int>(10);
auto p2 = p1;
七、weak_ptr(重点难点)
1、为什么需要 weak_ptr?
因为:
shared_ptr 存在循环引用问题
2、什么是循环引用?
例如:
class B;
class A
{
public:
shared_ptr<B> pb;
};
class B
{
public:
shared_ptr<A> pa;
};
创建对象:
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->pb = pb; // A对象的pb成员指向B对象
pb->pa = pa; // B对象的pa成员指向A对象
此时:
-
A 引用 B
-
B 引用 A
结果:
引用计数永远不为 0
因此:
内存泄漏
3、weak_ptr 的作用
weak_ptr
表示:
弱引用
特点:
-
不增加引用计数
-
不拥有资源
-
只是观察资源
4、解决循环引用
class B;
class A
{
public:
weak_ptr<B> pb;
};
class B
{
public:
weak_ptr<A> pa;
};
这样:
weak_ptr 不会增加计数
对象最终可以正常释放。
5、lock() 使用
weak_ptr 不能直接访问对象。
需要:
lock()
例如:
weak_ptr<int> wp;
if(auto sp = wp.lock())
{
cout << *sp << endl;
}
lock 后:
-
如果对象存在
-
返回 shared_ptr
-
-
如果对象已经释放
-
返回 nullptr
-
八、智能指针常见函数
1、reset()
释放当前资源。
p.reset();
也可以重新绑定:
p.reset(new int(20));
2、get()
获取原始指针。
int* raw = p.get();
注意:
⚠️不要手动 delete
⚠️否则会崩溃。
3、release()
仅 unique_ptr 有。
作用:
放弃所有权
例如:
unique_ptr<int> p(new int(10));
int* raw = p.release();
此时:
p == nullptr
需要手动:
delete raw;
九、智能指针的底层思想
核心其实是:
析构函数自动释放资源
例如:
class SmartPtr
{
private:
int* ptr;
public:
SmartPtr(int* p)
{
ptr = p;
}
~SmartPtr()
{
delete ptr;
}
};
当对象离开作用域:
~SmartPtr()
自动调用。
这就是 RAII 思想。
十、智能指针使用场景
1、unique_ptr 适合
-
独占资源
-
类成员
-
STL 容器
-
文件对象
-
socket对象
例如:
vector<unique_ptr<int>> vec;
2、shared_ptr 适合
-
多处共享资源
-
线程池
-
网络连接对象
-
回调系统
3、weak_ptr 适合
-
观察对象
-
解决循环引用
-
缓存系统
十一、实际开发建议
推荐优先级
unique_ptr > shared_ptr > 原始指针
原因:
-
unique_ptr 开销最小
-
shared_ptr 有引用计数
-
shared_ptr 存在线程安全成本
十二、shared_ptr 的线程安全
很多人误解:
shared_ptr 不是完全线程安全
准确来说:
-
引用计数是线程安全的
-
对对象本身访问不是线程安全的
例如:
shared_ptr<Test> p;
多个线程同时:
p->data++;
依然需要加锁。
十三、智能指针常见问题
1、智能指针解决了什么问题?
答:
-
自动释放内存
-
防止内存泄漏
-
RAII思想
2、unique_ptr 和 shared_ptr 区别?
| unique_ptr | shared_ptr |
|---|---|
| 独占 | 共享 |
| 不可拷贝 | 可拷贝 |
| 开销小 | 有引用计数开销 |
| 推荐优先使用 | 多人共享时使用 |
3、为什么需要 weak_ptr?
答:
解决 shared_ptr 循环引用
4、shared_ptr 底层原理?
答:
-
引用计数
-
计数加减
-
为 0 自动释放
十四、完整示例
#include <iostream>
#include <memory>
using namespace std;
class Person
{
public:
Person()
{
cout << "构造函数" << endl;
}
~Person()
{
cout << "析构函数" << endl;
}
};
int main()
{
{
shared_ptr<Person> p1 = make_shared<Person>();
cout << p1.use_count() << endl;
{
shared_ptr<Person> p2 = p1;
cout << p1.use_count() << endl;
}
cout << p1.use_count() << endl;
}
return 0;
}
输出:
构造函数
1
2
1
析构函数
十五、总结
智能指针核心思想
RAII
即:
用对象生命周期管理资源生命周期
三大智能指针总结
| 智能指针 | 特点 |
|---|---|
| unique_ptr | 独占资源 |
| shared_ptr | 共享资源 |
| weak_ptr | 弱引用,不增加计数 |
推荐
能用 unique_ptr 就不用 shared_ptr
因为:
-
更轻量
-
更安全
-
性能更好
最后一句
对于现代 C++:
尽量不要再直接使用 new/delete
而应该:
make_unique
make_shared
这已经是现代 C++ 开发的重要习惯。
-------------------------------------------------------------------------------------------------
如果文章对你有帮助的话,请点一个小小的赞谢谢
❤️么么么么么么么哒❤️
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)