cpp自学 day26(智能指针)
C++ 智能指针学习笔记
智能指针是 C++11 引入的重要特性,旨在自动管理动态分配的内存,有效解决传统裸指针(raw pointer)可能导致的内存泄漏、野指针、重复释放等问题。它们的核心思想是RAII (Resource Acquisition Is Initialization),即资源在对象构造时获取,在对象析构时自动释放。
🎯 智能指针的类型
C++ 主要提供了三种智能指针:unique_ptr
、shared_ptr
和 weak_ptr
。
1. unique_ptr
(独占所有权)
- 概念: 独占性指针。
unique_ptr
管理的内存只能由一个智能指针拥有。当unique_ptr
对象被销毁时,它所指向的内存会被自动释放。 - 特性:
- 独占性: 任何时候,只有一个
unique_ptr
可以指向给定的内存地址。 - 无法复制:
unique_ptr
不能被复制(=
操作符),但可以被移动(move()
)。这意味着所有权可以转移。 - 轻量: 大小与裸指针相同(通常),性能开销极小。
- 独占性: 任何时候,只有一个
- 何时使用:
- 当你确定某块内存资源只会被一个所有者管理时。
- 作为函数返回值(通过移动语义安全返回动态分配的对象)。
- 用作类成员,表示该类独占某个资源。
示例:
#include <iostream>
#include <memory> // 包含智能指针的头文件
#include <utility> // 包含 moveusing namespace std; // 使用命名空间,以便直接使用 cout, endl, unique_ptr 等class MyClass {
public:MyClass() { cout << "MyClass 构造" << endl; }~MyClass() { cout << "MyClass 析构" << endl; }void greet() { cout << "Hello from MyClass!" << endl; }
};void processUniquePtr(unique_ptr<MyClass> ptr) {if (ptr) {ptr->greet();}// ptr 在这里离开作用域,MyClass 对象被自动销毁
}int main() {// 1. 创建 unique_ptr// (1)推荐使用 make_unique 来创建,更安全、更高效unique_ptr<MyClass> p1 = make_unique<MyClass>(); if (p1) {p1->greet();}//(2)第二种创建 unique_ptr的方式//unique_ptr<MyClass> p1(new MyClass());// 2. 所有权转移 (移动)// unique_ptr<MyClass> p2 = p1; // 编译错误!不能复制unique_ptr<MyClass> p2 = move(p1); // p1 现在为空,p2 拥有所有权if (p1) {cout << "p1 仍然有效" << endl;} else {cout << "p1 已经为空" << endl; // 输出此行}if (p2) {p2->greet(); }// 3. 作为函数参数传递 (所有权转移)cout << "\n--- 调用 processUniquePtr ---" << endl;unique_ptr<MyClass> p3 = make_unique<MyClass>();processUniquePtr(move(p3)); // p3 的所有权转移到函数参数 ptr// p3 在这里也变为空cout << "\n--- main 函数结束 ---" << endl; // p2 离开作用域,MyClass 析构return 0;
}
make_unique与unique_ptr(new T())创建unique_ptr指针的区别
安全性(防范内存泄漏):
make_unique
更安全。 即使在某些复杂表达式中发生异常,它也能确保内存不会泄漏。它把内存分配和智能指针绑定做成了一个“原子操作”。unique_ptr(new T())
在极少数情况下(比如在函数参数求值时),如果new T()
后、unique_ptr
绑定前发生异常,可能导致内存泄漏。效率(内存分配次数):
make_unique
通常更高效。 它通常只需要一次内存分配,同时为对象和管理指针的数据分配空间。unique_ptr(new T())
涉及两次内存分配:一次给new T()
的对象,一次给unique_ptr
自身。
2. shared_ptr
(共享所有权)
- 概念: 共享性指针。
shared_ptr
允许多个智能指针共同拥有同一块内存资源。它通过引用计数 (reference count) 来管理内存,只有当所有指向该内存的shared_ptr
都被销毁时,内存才会被释放。 - 特性:
- 共享性: 可以被复制,多个
shared_ptr
可以指向同一个对象。 - 引用计数: 内部维护一个引用计数,记录有多少个
shared_ptr
指向同一个对象。 - 自动释放: 当引用计数归零时,内存自动释放。
- 循环引用问题: 存在循环引用(Cyclic Reference)的风险,可能导致内存泄漏(两个或多个
shared_ptr
相互引用,导致引用计数永远不为零)。
- 共享性: 可以被复制,多个
- 何时使用:
- 当一个内存资源可能被多个模块或对象共享时。
- 工厂方法返回对象时。
- 实现观察者模式或事件系统。
示例:
#include <iostream>
#include <memory> // 包含智能指针的头文件using namespace std; // 使用命名空间,以便直接使用 cout, endl, shared_ptr 等class MyClass {
public:MyClass() { cout << "MyClass 构造" << endl; }~MyClass() { cout << "MyClass 析构" << endl; }void greet() { cout << "Hello from MyClass!" << endl; }
};void showSharedPtr(shared_ptr<MyClass> ptr) { // 参数是 shared_ptr 副本cout << " 进入 showSharedPtr 函数,引用计数: " << ptr.use_count() << endl;if (ptr) {ptr->greet();}cout << " 离开 showSharedPtr 函数" << endl;// ptr 离开作用域,引用计数 -1
}int main() {// 1. 创建 shared_ptr// 推荐使用 make_shared 来创建,更安全、更高效shared_ptr<MyClass> sp1 = make_shared<MyClass>(); cout << "sp1 创建后,引用计数: " << sp1.use_count() << endl; // 输出: 1// 2. 复制 shared_ptr (共享所有权)shared_ptr<MyClass> sp2 = sp1; // 复制,引用计数增加cout << "sp2 复制后,引用计数: " << sp1.use_count() << endl; // 输出: 2// 3. 作为函数参数传递 (引用计数增加)cout << "\n--- 调用 showSharedPtr ---" << endl;showSharedPtr(sp1); // sp1 复制给函数参数 ptrcout << "--- showSharedPtr 返回后,sp1 引用计数: " << sp1.use_count() << endl; // 输出: 2 (因为函数参数的副本已销毁)// 4. 一个 shared_ptr 离开作用域{shared_ptr<MyClass> sp3 = sp1; // 又一个副本,引用计数增加到 3cout << "sp3 在作用域内,引用计数: " << sp1.use_count() << endl; // 输出: 3} // sp3 离开作用域,引用计数减少到 2cout << "sp3 离开作用域后,引用计数: " << sp1.use_count() << endl; // 输出: 2sp1.reset(); // 显式释放 sp1 持有的对象,引用计数 -1 (变为 1)cout << "sp1 reset 后,引用计数: " << sp2.use_count() << endl; // 输出: 1cout << "\n--- main 函数结束 ---" << endl; // sp2 离开作用域,引用计数 -1 (变为 0),MyClass 对象析构return 0;
}
3. weak_ptr
(非拥有性引用)
- 概念:
weak_ptr
是一种非拥有性的智能指针。它不增加所指向对象的引用计数,因此不会阻止对象被释放。 - 特性:
- 不拥有: 不拥有对象的管理权,因此不会增加引用计数。
- 解决循环引用: 主要用于解决
shared_ptr
的循环引用问题。 - 安全性:
weak_ptr
可以检查它所指向的对象是否仍然存在。如果对象已被销毁,weak_ptr
会变为空。 - 使用前必须转换为
shared_ptr
: 无法直接通过weak_ptr
访问对象,必须先通过lock()
方法获取一个shared_ptr
。
- 何时使用:
- 打破
shared_ptr
之间的循环引用。 - 观察者模式中,观察者持有被观察对象的
weak_ptr
,避免被观察者无法被释放。 - 缓存机制中,缓存项可以持有资源的
weak_ptr
,允许资源在内存不足时被清除。
- 打破
示例(解决循环引用):
#include <iostream>
#include <memory>using namespace std; // 使用命名空间class B; // 前向声明class A {
public:shared_ptr<B> b_ptr; // A 拥有 BA() { cout << "A 构造" << endl; }~A() { cout << "A 析构" << endl; }void set_b(shared_ptr<B> b) { b_ptr = b; }
};class B {
public:weak_ptr<A> a_ptr; // B 弱引用 A,不增加 A 的引用计数B() { cout << "B 构造" << endl; }~B() { cout << "B 析构" << endl; }void set_a(shared_ptr<A> a) { a_ptr = a; }void access_a() {if (shared_ptr<A> temp_a = a_ptr.lock()) { // 尝试获取 shared_ptrcout << " B 成功访问 A (引用计数: " << temp_a.use_count() << ")" << endl;} else {cout << " B 无法访问 A,A 已被销毁" << endl;}}
};int main() {cout << "--- 正常情况下(无循环引用)---" << endl;{shared_ptr<A> pa_normal = make_shared<A>();shared_ptr<B> pb_normal = make_shared<B>();// pa_normal 持有 pb_normal,但 pb_normal 不持有 pa_normalpa_normal->set_b(pb_normal); cout << "pa_normal 引用计数: " << pa_normal.use_count() << endl; // 1cout << "pb_normal 引用计数: " << pb_normal.use_count() << endl; // 2 (pa_normal 和 pb_normal 都持有)} // pa_normal, pb_normal 离开作用域,引用计数归零,对象正常析构cout << "\n--- 解决循环引用(使用 weak_ptr)---" << endl;// 这是演示 weak_ptr 解决 shared_ptr 循环引用的关键代码块{shared_ptr<A> pa = make_shared<A>(); // A 构造shared_ptr<B> pb = make_shared<B>(); // B 构造pa->set_b(pb); // A 强引用 B (b_ptr 是 shared_ptr)pb->set_a(pa); // B 弱引用 A (a_ptr 是 weak_ptr,不增加 A 的引用计数)cout << "pa 引用计数: " << pa.use_count() << endl; // 1 (只有 pa 自己)cout << "pb 引用计数: " << pb.use_count() << endl; // 2 (pa->b_ptr 和 pb 自己)pb->access_a(); // B 能够通过 weak_ptr 临时获取 A 的 shared_ptr 并访问} // pa, pb 离开作用域,引用计数归零,A 和 B 都正常析构cout << "\n--- main 函数结束 ---" << endl;return 0;
}
unique_ptr
常用方法清单
unique_ptr()
:创建空指针。unique_ptr(ptr)
:接管裸指针ptr
的所有权。unique_ptr(unique_ptr&& other)
:移动构造,从other
转移所有权。operator=(unique_ptr&& other)
:移动赋值,转移所有权。operator=(nullptr_t)
:将指针置空。get()
:返回存储的裸指针。release()
:释放所有权,返回裸指针并置空自身(返回的裸指针需手动管理)。reset(ptr = nullptr)
:释放当前对象,接管新裸指针ptr
的所有权(或置空)。swap(other)
:与另一个unique_ptr
交换对象。operator bool()
:判断指针是否为空。operator*()
:解引用,获取所指对象。operator->()
:箭头运算符,访问所指对象成员。
shared_ptr
常用方法清单
shared_ptr()
:创建空指针。shared_ptr(ptr)
:接管裸指针ptr
的所有权。shared_ptr(const shared_ptr& other)
:复制构造,增加引用计数。shared_ptr(shared_ptr&& other)
:移动构造,转移所有权(不改变引用计数)。shared_ptr(weak_ptr wp)
:从weak_ptr
构造shared_ptr
(如果对象存在)。operator=(const shared_ptr& other)
:复制赋值,增加引用计数。operator=(shared_ptr&& other)
:移动赋值。operator=(nullptr_t)
:将指针置空。use_count()
:返回当前指向同一对象的shared_ptr
数量。get()
:返回存储的裸指针。reset(ptr = nullptr)
:释放当前对象,接管新裸指针ptr
的所有权(或置空)。swap(other)
:与另一个shared_ptr
交换对象。operator bool()
:判断指针是否为空。operator*()
:解引用,获取所指对象。operator->()
:箭头运算符,访问所指对象成员。owner_before(other)
:比较拥有者次序(用于排序)。
weak_ptr
常用方法清单
weak_ptr()
:创建空指针。weak_ptr(const shared_ptr& sp)
:从shared_ptr
构造weak_ptr
。weak_ptr(const weak_ptr& wp)
:复制构造。weak_ptr(weak_ptr&& wp)
:移动构造。operator=(const shared_ptr& sp)
:从shared_ptr
赋值。operator=(const weak_ptr& wp)
:复制赋值。operator=(weak_ptr&& wp)
:移动赋值。lock()
:尝试获取一个shared_ptr
。如果对象存在则返回非空shared_ptr
,否则返回空。expired()
:检查所指对象是否已被销毁。use_count()
:返回所观察对象的shared_ptr
引用计数。reset()
:将weak_ptr
置空。swap(other)
:与另一个weak_ptr
交换对象。owner_before(other)
:比较拥有者次序。
💡 智能指针的优势
- 自动内存管理: 无需手动
delete
,避免内存泄漏。 - 避免野指针: 当智能指针离开作用域时,资源会被释放,指向该资源的智能指针也会失效。
- 异常安全: 即使在函数发生异常时,智能指针也能保证内存的正确释放。
- 代码简洁: 减少了手动管理内存的复杂性,使代码更清晰。
⚠️ 使用注意事项
- 总是使用
make_unique
和make_shared
: 这是创建智能指针的最佳实践,比直接使用new
更安全(防止异常和资源泄漏)和更高效。 - 避免裸指针和智能指针混用: 尽量不要将智能指针管理的对象的裸指针暴露出去,或在两者之间频繁转换,这容易造成混乱和错误。如果需要获取裸指针,使用
get()
方法,但要谨慎使用。 unique_ptr
不可复制,只能移动: 如果你需要转移所有权,请使用move()
。shared_ptr
的循环引用问题: 这是最常见的陷阱。当两个对象互相持有对方的shared_ptr
时,它们的引用计数永远不会归零,导致内存泄漏。使用weak_ptr
来打破循环引用。- 不要用同一个裸指针初始化多个智能指针: 这会导致重复释放和未定义行为。