C++11【智能指针】
智能指针:是 C++ 标准库提供的一种封装了原始指针的类模板,核心作用是自动管理动态内存,避免手动 new/delete 导致的内存泄漏(如:异常抛出时忘记释放内存)或重复释放等问题。它的本质是利用RAII(资源获取即初始化)机制:将动态内存的生命周期与智能指针对象的生命周期绑定 —— 当智能指针对象离开作用域时,其析构函数会自动调用 delete 释放所管理的内存。
1. 什么是智能指针?
智能指针:是 C++ 标准库提供的一种封装了原始指针的类模板,核心作用是自动管理动态内存,避免手动 new/delete 导致的内存泄漏(如:异常抛出时忘记释放内存)或重复释放等问题。
- 它的本质是利用RAII(资源获取即初始化)机制:将动态内存的生命周期与智能指针对象的生命周期绑定 —— 当智能指针对象离开作用域时,其析构函数会自动调用 delete 释放所管理的内存
2. 常用智能指针的类型有哪些?
三大常用智能指针的类型:
std::unique_ptr
- 基本特性:独占所有权,同一时间只能有一个 unique_ptr 管理某块内存,不允许拷贝,只能移动
- 适用场景:管理单个对象或数组,明确内存归属唯一的场景
代码语言:javascript
AI代码解释
#include <memory>
using namespace std;
int main()
{
//1.管理单个int对象
unique_ptr<int> ptr1(new int(10));
//2.或用更安全的make_unique(C++14)
auto ptr2 = make_unique<int>(20);
//3.不允许拷贝(编译报错)
// unique_ptr<int> ptr3 = ptr1;
//4.允许移动(所有权转移)
unique_ptr<int> ptr4 = move(ptr1);
} // 离开作用域时,ptr2、ptr4自动释放内存
std::shared_ptr
- 基本特性:共享所有权,通过引用计数记录有多少个 shared_ptr 管理同一块内存,当计数为 0 时自动释放
- 适用场景:需要多个指针共享同一资源(如:容器中存储的对象被多个地方引用)
代码语言:javascript
AI代码解释
#include <memory>
using namespace std;
int main()
{
auto ptr1 = make_shared<int>(30);
shared_ptr<int> ptr2 = ptr1; // 引用计数变为2
{ //使用大括号定义了一个 “临时代码块” ---> 定义了一个局部作用域
shared_ptr<int> ptr3 = ptr1; // 引用计数变为3
} // ptr3销毁,引用计数变回2
} // ptr1、ptr2销毁,引用计数变为0,内存释放
std::weak_ptr
- 基本特性:弱引用,不增加引用计数,用于解决 shared_ptr 的循环引用问题(两个 shared_ptr 互相引用导致计数无法归零)
- 适用场景:作为观察者关联共享资源,不参与所有权管理
代码语言:javascript
AI代码解释
#include <memory>
using namespace std;
struct Node
{
weak_ptr<Node> next; // 用weak_ptr避免循环引用
};
int main()
{
auto node1 = make_shared<Node>();
auto node2 = make_shared<Node>();
node1->next = node2; // weak_ptr不增加计数
node2->next = node1;
} // 计数正常归零,内存释放
总结:
- 独占资源 →
unique_ptr—> 适合 “一对一” 的场景 - 共享资源 →
shared_ptr—> 适合 “多对一” 的场景 - 观察资源 →
weak_ptr—> 解决循环引用
3. 怎么使用智能指针?
使用 C++ 智能指针的核心是利用其自动管理内存的特性,避免手动
new/delete导致的问题。 以下是三种常用智能指针的具体使用方法和场景:
一、std::unique_ptr(独占所有权)
代码语言:javascript
AI代码解释
#include <iostream>
#include <memory> // 需包含智能指针头文件
using namespace std;
int main()
{
/*--------------- 创建 unique_ptr(管理单个对象)---------------*/
//方式1:直接绑定 new 分配的内存(不推荐,存在异常安全风险)
unique_ptr<int> ptr1(new int(10));
//方式2:用 make_unique 创建(C++14 推荐,更安全)
auto ptr2 = make_unique<int>(20); // 自动推导类型
/*--------------- 访问所管理的对象(同普通指针)---------------*/
*ptr2 = 30; // 修改值
cout << *ptr2 << endl; // 输出:30
/*--------------- 转移所有权(只能用 move,原指针会变为空)---------------*/
unique_ptr<int> ptr3 = move(ptr2); //注意:ptr2 不再拥有内存
if (ptr2 == nullptr)
{
cout << "ptr2 已为空" << endl;
}
/*--------------- 管理动态数组(需指定数组类型)---------------*/
unique_ptr<int[]> arr_ptr = make_unique<int[]>(5); // 5个int的数组
arr_ptr[0] = 1; // 数组访问
}
// 离开作用域时,所有 unique_ptr 自动释放内存(无需手动 delete)

在这里插入图片描述
二、std::shared_ptr(共享所有权)
代码语言:javascript
AI代码解释
#include <iostream>
#include <memory>
using namespace std;
int main()
{
//1.创建 shared_ptr(推荐用 make_shared)
auto ptr1 = make_shared<int>(100); // 引用计数 = 1
//2.共享所有权(拷贝指针时,引用计数增加)
shared_ptr<int> ptr2 = ptr1; // 引用计数 = 2
shared_ptr<int> ptr3 = ptr2; // 引用计数 = 3
//3.访问对象(同普通指针)
*ptr3 = 200;
cout << *ptr1 << endl; // 输出:200(所有指针指向同一内存)
//4.查看引用计数(use_count() 仅用于调试)
cout << "计数:" << ptr1.use_count() << endl; // 输出:3
//5.局部作用域演示计数变化
{
shared_ptr<int> ptr4 = ptr1; // 计数 = 4
} // ptr4 销毁,计数 = 3
//6.手动释放(重置指针,计数减少)
ptr2.reset(); // ptr2 不再指向内存,计数 = 2
}
// ptr1、ptr3 销毁,计数 = 0 → 内存自动释放

在这里插入图片描述
三、std::weak_ptr(弱引用,解决循环引用)
代码语言:javascript
AI代码解释
#include <iostream>
#include <memory> // 包含智能指针所需的头文件
using namespace std;
// 定义链表节点结构
// 场景:链表节点之间可能互相引用,容易引发shared_ptr的循环引用问题
struct Node
{
int value; // 节点存储的值
weak_ptr<Node> next; // 指向链表下一个节点的弱指针
//关键:使用weak_ptr而非shared_ptr,避免循环引用
};
int main()
{
//1.创建两个共享指针,分别管理两个Node对象
auto node1 = make_shared<Node>(); // node1的引用计数为1
auto node2 = make_shared<Node>(); // node2的引用计数为1
//注意: make_shared是创建shared_ptr的推荐方式,安全且高效
//2.构建节点间的互相引用关系
node1->next = node2; // weak_ptr接收shared_ptr时,不增加node2的引用计数(仍为1)
node2->next = node1; // 同理,node1的引用计数仍为1
//注意:若此处用shared_ptr,会导致引用计数循环增加,无法归零
//3.1:temp是有效的shared_ptr,说明node2仍存在
if (auto temp = node1->next.lock()) //注意:访问weak_ptr指向的对象:必须通过lock()方法转换为shared_ptr
{
cout << "node2 有效" << endl;
}
//3.2:若node2已被释放,进入此分支
else
{
cout << "node2 已释放" << endl;
}
/* 说明:lock()的作用:检查被引用的对象是否还存在
* 1. 若存在:返回一个指向该对象的shared_ptr(此时引用计数临时+1)
* 2. 若已释放:返回空的shared_ptr
*/
} // main函数结束,局部变量node1和node2开始销毁
// 1. node2的引用计数从1减为0 → 其管理的Node对象被释放
// 2. node1的引用计数从1减为0 → 其管理的Node对象被释放
// 3. 由于使用weak_ptr,没有循环引用,所有内存正常释放(无内存泄漏)

在这里插入图片描述
4. 为什么需要智能指针?
在 C++ 中,智能指针的出现主要是为了:解决手动管理动态内存时容易出现的问题,其核心价值在于自动管理内存生命周期,避免内存相关的 bug。
具体来说,需要智能指针的原因可以从以下几个方面理解:
1. 避免内存泄漏
手动管理动态内存(使用new分配、delete释放)时,若因逻辑疏漏导致delete未执行,会造成内存泄漏(已分配的内存无法回收,直到程序结束)
代码语言:javascript
AI代码解释
void func()
{
int* ptr = new int(10); // 分配内存
if (someCondition)
{
return; // 提前返回,导致后续的delete未执行
}
delete ptr; // 若if条件成立,此行不会执行,内存泄漏
}
智能指针会在自身生命周期结束时(如:超出作用域、被销毁)自动调用delete,无论程序执行路径如何(即使有提前返回、异常抛出等),都能保证内存被释放
2. 防止重复释放
手动释放内存时,若对同一块内存多次调用delete,会导致未定义行为(程序崩溃、数据损坏等)
代码语言:javascript
AI代码解释
void func()
{
int* ptr1 = new int(10);
int* ptr2 = ptr; // 两个指针指向同一块内存
delete ptr1;
delete ptr2; // 重复释放,行为未定义
}
智能指针通过引用计数等机制追踪内存的引用情况,只有当最后一个引用它的智能指针被销毁时,才会真正释放内存,避免重复释放
3. 应对异常安全
当程序抛出异常时,手动管理的内存可能因delete语句被跳过而泄漏
代码语言:javascript
AI代码解释
void func()
{
int* ptr = new int(10);
try
{
someOperation(); // 若此函数抛出异常
}
catch (...)
{
// 如果发生了异常:且未在catch中手动释放ptr,内存泄漏
throw;
}
delete ptr; // 如果未发生了异常:ptr指向的资源将在这里释放
}
智能指针的析构函数会在异常发生时被自动调用(C++ 的栈展开机制),确保内存释放,无需手动在异常处理中额外处理
更多推荐


所有评论(0)