智能指针与RAII
需要预先了解:
1. 原始指针的缺点
原始指针/裸指针:如int* p,需要手动管理内存,容易造成以下问题:
1. 忘记delete/free会导致内存泄露:
// 局部变量 ptr 指向的堆内存未释放,造成内存泄露
void func
{
Type* ptr = new Type();
ptr->do_something();
}
2.使用释放后的悬挂指针,导致未定义行为:
Type* ptr = new Type();
delete ptr;
ptr->do_something();
3. 重复释放同一块内存,导致未定义行为或程序崩溃
Type* ptr = new Type();
delete ptr;
delete ptr; // 重复释放同一块内存
2. RAII(Resource Acquisition Is Initialization)
RAII(直译为“资源获取即初始化”):在 对象构造阶段获取资源,在 对象析构阶段释放资源;依靠 C++ 对象自动生命周期管理(离开作用域自动调用析构),保证资源一定会被释放。
一个实现RAII管理资源的简单设计:
// Array.h
class Array
{
private:
int* data;
size_t size;
public:
Array(size_t size);
~Array();
// 不允许浅拷贝,可以看需求决定是否允许深拷贝
Array(const Array& other) = delete;
Array& operator=(const Array& other) = delete;
// 视情况决定是否允许移动
Array(Array&& other);
Array& operator=(Array&& other);
}
这里给出函数的具体实现供参考:
// Array.cpp
Array::Array(size_t size)
{
data = new int[size]();
}
Array::~Array()
{
std::cout << "析构函数被调用,在这里释放内存" << std::endl;
delete[] data;
}
Array::Array(Array&& other)
{
data = other.data;
other.data = nullptr;
}
Array& Array::operator=(Array&& other)
{
if(this != &other)
{
data = other.data;
other.data = nullptr;
}
return *this;
}
3. 智能指针
智能指针是原始指针的封装,会通过RAII的模式自动管理内存,确保资源在不再使用时被正确释放,避免内存泄漏和指针悬空等问题。智能指针定义在头文件<memory>,常见的智能指针有:
std::unique_ptrstd::shared_ptrstd::weak_ptr
3.1 std::unique_ptr:独占指针
独占语义:同一时刻,只能有一个 unique_ptr 拥有这块内存
- 禁止拷贝(拷贝构造与拷贝赋值)
- 允许移动进行所有权的转移
- 当
std::shared_ptr被销毁时,资源会被自动释放
1. std::shared_ptr的创建:语法为std::shared_ptr<变量类型>
#include <memory>
// 通过 原始指针 或 new 进行创建
int* raw_ptr = new int(10);
std::unique_ptr<int> uptr1(raw_ptr);
std::unique_ptr<int> uptr2(new int(20));
raw_ptr = nullptr; // 注意把 raw_ptr 置空,确保资源被 uptr1 独占
// 通过 make_unique 进行创建,最推荐最安全的方式
auto uptr3 = std::make_unique<int>(30);
2. 和裸指针一样,通过 -> 和 * 操作符访问资源:
std::unique_ptr<MyClass> uptr1 = std::make_unique<MyClass>();
uptr1->member_function();
auto uptr2 = std::make_unique<int>();
std::cout << *uptr2 << std::endl;
3. 可以通过 get() 方法获取地址/访问原始指针:
int *rawPtr = uniquePtr.get();
4. 允许移动:
std::unique_ptr<int> uniquePtr1 = std::make_unique<int>(10);
std::unique_ptr<int> uniquePtr2 = std::move(uniquePtr1); // 转移所有权
if(!uniquePtr1) {
std::cout << "uniquePtr1 is empty" << std::endl; // uniquePtr1 现在为空
}
5. 禁止拷贝:
std::unique_ptr<Data> a = std::make_unique<Data>();
std::unique_ptr<Data> b = a; // 编译报错!禁止拷贝
3.2 std::shared_ptr共享指针 与 std::weak_ptr弱指针
std::shared_ptr
- 共享所有权:多个
std::shared_ptr可以指向同一个对象 - 控制块:在堆上分配,独立于管理对象;其中保存着强引用计数、弱引用计数与删除器
- 规则:
- 拷贝
shared_ptr,则强引用计数+1 shared_ptr析构/被赋值覆盖,则强引用计数-1- 当强引用计数为0时,销毁托管对象
- 当
weak_ptr全部销毁后释放控制块
- 拷贝
1. 创建std::shared_ptr:语法为std::shared_ptr<变量类型>
#include <memory>
// 通过原始指针创建
int *raw_ptr = new int(10);
std::shared_ptr<int> sptr1(raw_ptr);
raw_ptr = nullptr;
// 通过 new 创建
std::shared_ptr<int> sptr2(new int(20));
// 通过 make_shared 创建,最推荐的方式
std::shared_ptr<int> sptr3 = std::make_shared<int>(30);
2. 获取引用计数:通过use_count()方法获取持有当前资源的shared_ptr的数目。
3. 释放所有权:通过reset()方法释放shared_ptr
std::shared_ptr<int> sptr1 = std::make_shared<int>(10);
std::shared_ptr<int> sptr2 = sptr1; // 共享资源,引用计数增加
std::cout << "引用计数: " << sptr1.use_count() << std::endl; // 输出 2
sptr2.reset(); // 释放 sptr2 的资源,引用计数减少
std::cout << "引用计数: " << sptr1.use_count() << std::endl; // 输出 1
sptr1.reset(); // 释放 sptr1 的资源,引用计数减少到 0,资源被释放
4. 获取裸指针:通过get()方法
auto sptr = std::make_shared<int>(10);
int* raw_ptr = sptr.get();
std::weak_ptr:
- 不拥有所有权,不影响强引用计数
- 不能直接访问对象,需要调用
lock()方法提升为shared_ptr- 如果资源已经被释放,则返回一个空的
shared_ptr - 如果资源未被释放,则返回一个指向资源的
shared_ptr
- 如果资源已经被释放,则返回一个空的
- 不能由裸指针构造,只能由
shared_ptr/weak_ptr赋值得到
auto sptr = std::make_shared<Node>();
std::weak_ptr<Node> wptr = sptr;
if(auto temp_ptr = wptr.lock())
{
temp_ptr->do_something();
}
循环引用问题
两个对象互相持有对方的shared_ptr,导致引用计数永远无法归零,进而使得内存泄漏。
下面是一个循环引用的示例:
struct Node
{
int val;
std::shared_ptr<Node> partner;
Node(int v) : val(v) {}
};
void func()
{
auto a = std::make_shared<Node>(1);
auto b = std::make_shared<Node>(2);
a->partner = b, b->partner = a; // a,b互相持有对方
}
在函数func()调用时,堆上的两个Node对象的的shared_ptr的引用计数都是2,但是当func()结束后,a、b被销毁,引用计数从2变成了1,对象不会被析构,造成了内存泄漏。
解决方案:把其中一个shared_ptr换为weak_ptr:
struct Node
{
int val;
std::weak_ptr<Node> next; // 改成 weak_ptr
Node(int v) : val(v) {}
};


