C++11引入了智能指针,用于自动管理动态内存的释放,避免内存泄漏。常用的智能指针有 unique_ptr、shared_ptr 和 weak_ptr,每种类型的智能指针有不同的特点和使用场景。
头文件为<memory>
1. unique_ptr 智能指针
unique_ptr 是独占指针,即某个动态分配的内存只能被一个 unique_ptr 所拥有,不支持拷贝和赋值操作。它会在超出作用域时自动释放资源。
示例代码:
cpp
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> p1(new int(24));
cout << "*p1 = " << *p1 << endl << endl;
unique_ptr<int> p2 = move(p1); // 使用 move 转移所有权
cout << "*p2 = " << *p2 << endl << endl;
p2.reset(); // 显式释放内存
p1.reset();
unique_ptr<int> p3(new int(250));
p3.reset(new int(666)); // 绑定动态对象
cout << "*p3 = " << *p3 << endl << endl;
p3 = nullptr; // 显式销毁指向的对象,同时智能指针变为空,等价于 p3.reset()
unique_ptr<int> p4(new int(999));
int* p = p4.release(); // 释放控制权,内存不释放
cout << "*p = " << *p << endl << endl;
// p4 已经没有管理任何对象,访问会导致错误
// cout << "*p4 = " << *p4 << endl;
delete p; // 手动释放内存
return 0;
}
输出结果:
bash
*p1 = 24
*p2 = 24
*p3 = 666
*p = 999
关键点:
-
unique_ptr不能拷贝,只能通过std::move转移所有权。 -
reset()用来释放智能指针管理的内存,release()用来转移控制权但不释放内存。
2. shared_ptr 智能指针
shared_ptr 是共享指针,多个 shared_ptr 可以指向同一块动态内存,并通过引用计数的方式管理内存的生命周期。引用计数为零时,资源会被释放。
shared_ptr 的核心是一个叫 控制块(control block) 的结构体,它记录了以下两个关键信息:
-
指向资源的原始指针(void *ptr)
-
引用计数(ref_count)
可以把它想象成这样(伪结构体):
cpp
struct ControlBlock {
int ref_count; // 当前有多少 shared_ptr 指向这块内存
void* ptr; // 指向实际数据的指针
};
示例代码:
cpp
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> p1(new int(456));
shared_ptr<int> p2 = p1; // 共享所有权
cout << "p2 引用计数: " << p2.use_count() << endl << endl;
cout << "*p1 = " << *p1 << endl;
cout << "*p2 = " << *p2 << endl;
cout << "p2 引用计数: " << p2.use_count() << endl << endl;
p1.reset(); // p1 不再管理内存
cout << "p2 引用计数: " << p2.use_count() << endl << endl;
return 0;
}
代码解释:
-
每个
shared_ptr都会共享同一个"引用计数控制块"。 -
当新的
shared_ptr被复制时,引用计数 +1; -
当某个
shared_ptr调用reset()或超出作用域时,引用计数 -1; -
当引用计数减为 0 时,内存会被自动释放。
输出结果:
bash
p2 引用计数: 2
*p1 = 456
*p2 = 456
p2 引用计数: 2
p2 引用计数: 1
关键点:
-
shared_ptr会自动管理内存,通过引用计数来确保内存安全。 -
use_count()用来获取当前对象的引用计数。 -
reset()用来重置shared_ptr,使其释放控制的内存。
3. make_shared 函数
make_shared 是创建 shared_ptr 的推荐方法,它通过单次内存分配实现了更高效的内存管理。它不仅分配内存给对象,还分配了用于引用计数的内存。
示例:
cpp
auto p = make_shared<int>(10);
与 shared_ptr(new int(10)) 的区别在于:
make_shared会一次性分配内存,效率更高。
例如:
(1)shared_ptr:
cpp
shared_ptr<int> p1(new int(10));
这里是创建一个 shared_ptr,同时通过 new 操作符分配了一个 int 类型的动态内存。这样,shared_ptr 管理的是一个指向动态内存的原始指针。首先,new int(10) 分配了内存给 int 类型的对象,然后 shared_ptr 会为引用计数分配内存。因此总共进行了 两次内存分配 :一次是为对象本身分配内存(即 int 类型的内存),一次是为 shared_ptr 维护的引用计数分配内存
(2)make_shared:
cpp
auto p2 = make_shared<int>(10);
使用 make_shared,它会一次性分配内存,同时为对象本身和引用计数分配内存。即,make_shared 只需要 一次内存分配,同时包含了对象和引用计数的信息。
- 它避免了内存泄漏的风险,特别是在异常情况下。
4. weak_ptr 智能指针
weak_ptr 是辅助 shared_ptr 使用的智能指针,它不增加或减少引用计数,因此它并不拥有资源的所有权。weak_ptr 不能直接访问资源,但可以通过 lock() 方法将其转化为 shared_ptr 进行访问。
示例代码:
cpp
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> p1(new int(300));
shared_ptr<int> p2 = p1;
weak_ptr<int> wp = p1; // weak_ptr 不增加引用计数
cout << "wp 引用计数: " << wp.use_count() << endl << endl;
cout << "*p1 = " << *p1 << endl;
cout << "*p2 = " << *p2 << endl;
cout << endl;
return 0;
}
输出结果:
bash
wp 引用计数: 2
*p1 = 300
*p2 = 300
关键点:
-
weak_ptr不拥有资源的所有权,不能直接使用资源。 -
使用
lock()方法可以获得一个shared_ptr,从而访问资源。
5.总结
-
unique_ptr:独占资源,无法拷贝或赋值,适用于资源的独占管理。 -
shared_ptr:共享资源,通过引用计数管理内存,适用于多个对象需要共享同一资源的场景。 -
weak_ptr:辅助shared_ptr,不增加引用计数,避免循环引用,适用于观察者模式等场景。