1. 为什么需要智能指针?
在传统 C++ 里,动态分配内存需要手动 new 和 delete。比如:
1int* p = new int(10);
2// 使用 p
3delete p; // 必须记得 delete
问题:
忘记
delete→ 内存泄漏。重复
delete→ 程序崩溃。提前
delete→ 悬空指针(指针还在,但指向的内存已经无效)。
智能指针就是为了解决这些痛点的。
2. C++11 提供了哪些智能指针?
| 智能指针 | 说明 |
|---|---|
std::unique_ptr | 独占式智能指针,一个对象只能有一个 unique_ptr 拥有。不能复制,只能移动。 |
std::shared_ptr | 共享式智能指针,多个 shared_ptr 可以共同拥有一个对象。内部有引用计数。 |
std::weak_ptr | 弱引用智能指针,不拥有对象,只是观察。配合 shared_ptr 使用,防止循环引用。 |
3. 各种智能指针的详细讲解
3.1 std::unique_ptr
特点:
资源独占,不能复制,只能移动。
析构时自动
delete管理的对象。
示例:
1#include <memory>
2#include <iostream>
3
4int main() {
5 std::unique_ptr<int> uptr(new int(42));
6 std::cout << *uptr << std::endl;
7
8 // std::unique_ptr<int> uptr2 = uptr; // 错误,不能复制
9 std::unique_ptr<int> uptr2 = std::move(uptr); // 可以移动
10
11 if (!uptr) {
12 std::cout << "uptr为空了" << std::endl;
13 }
14}
注意:
std::move转移所有权。推荐用
std::make_unique(C++14 加的)创建:
1auto uptr = std::make_unique<int>(42);
3.2 std::shared_ptr
特点:
多个
shared_ptr可以共同拥有一个对象。内部有一个引用计数器,记录有多少个
shared_ptr指向这个对象。最后一个
shared_ptr销毁时,才释放资源。
示例:
1#include <memory>
2#include <iostream>
3
4int main() {
5 std::shared_ptr<int> sptr1 = std::make_shared<int>(100);
6 std::shared_ptr<int> sptr2 = sptr1; // 引用计数 +1
7
8 std::cout << "引用计数: " << sptr1.use_count() << std::endl; // 输出 2
9}
注意:
make_shared效率高,推荐用。多个
shared_ptr指向同一资源没问题。
内部原理:
shared_ptr内部有一块小内存,记录引用计数。每次拷贝
+1,销毁-1,归零就delete。
3.3 std::weak_ptr
特点:
weak_ptr不影响引用计数。主要用于观察 shared_ptr 管理的对象,防止循环引用。
示例:
1#include <memory>
2#include <iostream>
3
4int main() {
5 std::shared_ptr<int> sptr = std::make_shared<int>(200);
6 std::weak_ptr<int> wptr = sptr;
7
8 std::cout << "引用计数: " << sptr.use_count() << std::endl; // 1,不变
9
10 if (auto spt = wptr.lock()) { // 尝试拿到 shared_ptr
11 std::cout << *spt << std::endl;
12 } else {
13 std::cout << "对象已经被释放" << std::endl;
14 }
15}
lock() 方法:
wptr.lock()返回一个shared_ptr。如果资源还活着,就能成功;否则返回
nullptr。
4. 三者的关系小总结
unique_ptr:独占。不能拷贝,只能转移。shared_ptr:共享。内部引用计数。weak_ptr:观察,不拥有,不增加计数。
5. 使用智能指针的注意事项
✅ 推荐用 make_shared 和 make_unique 创建对象,效率高又安全。
✅ 注意避免循环引用(两个对象相互持有 shared_ptr → 资源释放不了,要用 weak_ptr 打破环)。
✅ 不要手动 delete 智能指针管理的对象(会出大问题)。
✅ 小心从原始指针(T*)转换成智能指针时的所有权问题。
6. 小例子:智能指针管理类对象
1#include <memory>
2#include <iostream>
3
4class Test {
5public:
6 Test() { std::cout << "Test constructed" << std::endl; }
7 ~Test() { std::cout << "Test destructed" << std::endl; }
8};
9
10int main() {
11 {
12 auto ptr = std::make_shared<Test>();
13 } // 离开作用域,自动析构
14 std::cout << "End of main" << std::endl;
15}
输出:
Test constructed
Test destructed
End of main
智能指针帮你自动管理生命周期,不用手动 delete,非常优雅!
7. 智能指针的底层机制(稍微技术一点)
7.1 shared_ptr 的内部结构
可以理解成它维护了两样东西:
资源指针:真正指向管理对象的指针。
控制块(Control Block):保存引用计数、弱引用计数等信息。
大概结构像这样(画个简图给你看):
+----------------+ +-----------------+
| shared_ptr A | | Control Block |
| ------------- | -----> | - strong_count=2 |
| - ptr | | - weak_count=1 |
+----------------+ +-----------------+
|
+----------------+
| Managed Obj |
+----------------+
strong_count:多少个shared_ptr正在管理对象。weak_count:多少个weak_ptr观察这个对象。
当 strong_count == 0:
资源对象释放。
但控制块还保留(因为可能还有
weak_ptr)。
当 strong_count == 0 && weak_count == 0:
- 控制块也释放。
7.2 unique_ptr 的内部结构
超级简单:
只有一个裸指针。
也没有控制块,也没有引用计数。
就是一个单纯的拥有者,生命周期清清爽爽。
8. 智能指针的自定义删除器
有时候,delete 不是你想要的销毁方式,比如释放文件句柄、释放套接字,就可以用自定义删除器。
8.1 unique_ptr 自定义删除器
1#include <memory>
2#include <iostream>
3
4struct FileCloser {
5 void operator()(FILE* fp) const {
6 if (fp) {
7 fclose(fp);
8 std::cout << "File closed" << std::endl;
9 }
10 }
11};
12
13int main() {
14 std::unique_ptr<FILE, FileCloser> fp(fopen("test.txt", "w"));
15 if (fp) {
16 fputs("Hello, World!", fp.get());
17 }
18}
这里
FileCloser是一个仿函数,专门干**fclose**工作。unique_ptr可以带一个删除器类型参数。
8.2 shared_ptr 自定义删除器
1#include <memory>
2#include <iostream>
3
4int main() {
5 auto deleter = [](int* p) {
6 std::cout << "Deleting int: " << *p << std::endl;
7 delete p;
8 };
9
10 std::shared_ptr<int> sptr(new int(10), deleter);
11}
shared_ptr也支持自定义删除器,常用 lambda 表达式写。
9. 小心智能指针的坑
9.1 不要多次管理同一个裸指针!
错误示例:
1int* p = new int(5);
2std::shared_ptr<int> sp1(p);
3std::shared_ptr<int> sp2(p); // ❌ 两个shared_ptr都以为自己该负责delete!
- 结果:程序崩溃,双重释放。
✅ 正确做法是,只交给一个智能指针管理:
1auto sp1 = std::make_shared<int>(5);
2// 需要共享,用拷贝sp1即可。
3auto sp2 = sp1;
10. 总结大图!
我画了一张总结图,来帮你快速记忆:
【智能指针总结】
┌──────────────┐
│ unique_ptr │
│ - 独占资源 │
│ - 不可拷贝 │
│ - 可移动 │
│ - 轻量快 │
└──────────────┘
↓ 转移所有权 (move)
┌──────────────┐
│ shared_ptr │
│ - 共享资源 │
│ - 引用计数 │
│ - 自动释放 │
└──────────────┘
↓ 观察对象
┌──────────────┐
│ weak_ptr │
│ - 不拥有资源 │
│ - 不增加计数 │
│ - 防止循环引用│
└──────────────┘
11. 小扩展:什么时候用哪种智能指针?
| 场景 | 推荐指针 |
|---|---|
| 资源归属唯一,简单的对象生命周期 | unique_ptr |
| 资源需要多个对象共享 | shared_ptr |
| 需要观察但不管理资源,防止环状引用 | weak_ptr |
实际开发中:
能用
unique_ptr就用unique_ptr,开销最小。只有确实需要共享所有权时才用
shared_ptr。避免循环引用时配合
weak_ptr。
🎯 案例一:unique_ptr 管理动态资源
场景:管理单个对象,保证无拷贝,自动释放。
1#include <iostream>
2#include <memory>
3
4class Widget {
5public:
6 Widget() { std::cout << "Widget created\n"; }
7 ~Widget() { std::cout << "Widget destroyed\n"; }
8 void operation() { std::cout << "Widget operation\n"; }
9};
10
11void useWidget() {
12 std::unique_ptr<Widget> w = std::make_unique<Widget>();
13 w->operation(); // 使用对象
14} // 离开作用域,自动销毁 Widget
15
16int main() {
17 useWidget();
18 return 0;
19}
要点:
使用
std::make_unique创建对象。不需要手动
delete,更安全。
🎯 案例二:shared_ptr 多个共享者共同管理对象
场景:多个模块或组件需要共享访问同一资源。
1#include <iostream>
2#include <memory>
3
4class Data {
5public:
6 Data(int v) : value(v) { std::cout << "Data created\n"; }
7 ~Data() { std::cout << "Data destroyed\n"; }
8 void show() const { std::cout << "Value: " << value << "\n"; }
9private:
10 int value;
11};
12
13void processData(std::shared_ptr<Data> p) {
14 p->show();
15}
16
17int main() {
18 std::shared_ptr<Data> data = std::make_shared<Data>(42);
19 processData(data);
20 processData(data);
21
22 std::cout << "Use count: " << data.use_count() << "\n"; // 1
23 return 0;
24}
要点:
shared_ptr的引用计数自动管理资源。可以安全地跨函数、跨模块传递。
🎯 案例三:用 weak_ptr 避免循环引用
场景:两个对象互相持有指针,容易形成循环引用。
1#include <iostream>
2#include <memory>
3
4class B; // 前向声明
5
6class A {
7public:
8 std::shared_ptr<B> b_ptr;
9 ~A() { std::cout << "A destroyed\n"; }
10};
11
12class B {
13public:
14 std::weak_ptr<A> a_ptr; // 用 weak_ptr 破坏循环引用
15 ~B() { std::cout << "B destroyed\n"; }
16};
17
18int main() {
19 auto a = std::make_shared<A>();
20 auto b = std::make_shared<B>();
21 a->b_ptr = b;
22 b->a_ptr = a;
23
24 // 两者离开作用域时能正确释放
25 return 0;
26}
如果用 shared_ptr 互相持有,就会造成内存泄漏!
用 weak_ptr 打破强引用链,可以正常释放。
🎯 案例四:自定义删除器 shared_ptr 管理非堆内存
场景:管理资源,比如文件描述符、网络连接等,不能直接 delete。
1#include <iostream>
2#include <memory>
3#include <cstdio> // C文件操作
4
5void customDeleter(FILE* fp) {
6 if (fp) {
7 std::cout << "Closing file\n";
8 fclose(fp);
9 }
10}
11
12int main() {
13 std::shared_ptr<FILE> file(
14 fopen("test.txt", "w"), customDeleter);
15
16 if (file) {
17 fputs("Hello, world!\n", file.get());
18 }
19 // 文件会在 shared_ptr 销毁时自动关闭
20 return 0;
21}
要点:
shared_ptr支持自定义删除器。可以管理非 new 分配的资源。
🎯 案例五:智能指针搭配容器使用
场景:存储一组对象,自动管理生命周期。
1#include <iostream>
2#include <vector>
3#include <memory>
4
5class Item {
6public:
7 Item(int id) : id(id) { std::cout << "Item " << id << " created\n"; }
8 ~Item() { std::cout << "Item " << id << " destroyed\n"; }
9 void show() const { std::cout << "Item ID: " << id << "\n"; }
10private:
11 int id;
12};
13
14int main() {
15 std::vector<std::unique_ptr<Item>> items;
16 items.push_back(std::make_unique<Item>(1));
17 items.push_back(std::make_unique<Item>(2));
18 items.push_back(std::make_unique<Item>(3));
19
20 for (const auto& item : items) {
21 item->show();
22 }
23 // 离开作用域,所有 Item 自动销毁
24 return 0;
25}
要点:
unique_ptr不能拷贝,但可以移动,因此可以存到std::vector中。避免忘记手动
delete,特别适合资源密集型应用。
🎯 总结一下这些案例的核心套路:
| 场景 | 选择的智能指针 | 备注 |
|---|---|---|
| 单独管理一个对象 | unique_ptr | 独占资源,支持移动 |
| 多人共享资源 | shared_ptr | 自动引用计数 |
| 观察共享对象,不持有 | weak_ptr | 防止循环引用 |
| 管理非堆内存资源 | shared_ptr + 自定义删除器 | 如文件、socket |
| 容器中管理对象 | vector<unique_ptr<T>> | 元素安全释放 |
最后修改于 2025-04-27 18:22