梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
本教程将从 C++ 智能指针的核心概念、实现原理,到 C++11 标准中各类智能指针(std::unique_ptr、std::shared_ptr、std::weak_ptr)的使用方法、特性及场景,再到 std::auto_ptr 的弃用原因,全面拆解智能指针的核心知识点,帮助你解决手动管理内存带来的泄漏、野指针等问题。
智能指针是 C++ 标准库提供的模板类,本质是对普通裸指针的封装,利用RAII(资源获取即初始化)思想,在对象生命周期结束(析构时)自动释放所管理的内存资源,避免手动调用 delete 导致的内存泄漏、野指针、二次释放等问题。
智能指针的核心目标:将内存资源的生命周期与智能指针对象的生命周期绑定,实现内存的自动管理。
使用裸指针手动管理堆内存时,常见问题:
delete,或因异常跳转导致 delete 未执行;delete,导致程序崩溃;
// 手动内存管理的典型问题示例
void badExample() {
int* p = new int(10); // 分配堆内存
if (true) {
return; // 提前返回,delete未执行 → 内存泄漏
}
delete p; // 永远不会执行
p = nullptr;
}
智能指针基于 RAII 思想 实现,核心步骤:
*)、箭头(->)重载,模拟裸指针的使用方式;delete 释放管理的内存;
// 简易智能指针实现(仅演示原理)
template <typename T>
class SimpleSmartPtr {
private:
T* m_ptr; // 管理的裸指针
public:
// 构造函数:接管裸指针
explicit SimpleSmartPtr(T* ptr = nullptr) : m_ptr(ptr) {}
// 析构函数:自动释放内存
~SimpleSmartPtr() {
delete m_ptr;
m_ptr = nullptr;
}
// 禁用拷贝构造(避免所有权拷贝)
SimpleSmartPtr(const SimpleSmartPtr&) = delete;
// 禁用赋值运算符
SimpleSmartPtr& operator=(const SimpleSmartPtr&) = delete;
// 重载解引用运算符
T& operator*() const {
return *m_ptr;
}
// 重载箭头运算符
T* operator->() const {
return m_ptr;
}
};
// 使用简易智能指针
void goodExample() {
SimpleSmartPtr<int> p(new int(10)); // 接管内存
*p = 20; // 正常使用
// 函数结束,p析构 → 自动释放内存,无泄漏
}
std::unique_ptr 是 C++11 主推的独占所有权智能指针:
unique_ptr 指向某块内存;
#include <memory> // 必须包含头文件
#include <iostream>
using namespace std;
int main() {
// 1. 创建unique_ptr(推荐使用make_unique,C++14引入)
unique_ptr<int> up1(new int(10));
unique_ptr<int> up2 = make_unique<int>>(20); // 更安全,避免内存泄漏
// 2. 访问数据
cout << *up1 << endl; // 输出:10
cout << *up2 << endl; // 输出:20
// 3. 所有权转移(移动语义)
unique_ptr<int> up3 = move(up1); // up1失去所有权,变为空
if (up1 == nullptr) {
cout << "up1已失去所有权" << endl;
}
cout << *up3 << endl; // 输出:10
// 4. 释放所有权(手动)
int* rawPtr = up3.release(); // up3释放所有权,返回裸指针
delete rawPtr; // 需手动释放
rawPtr = nullptr;
// 5. 重置指针
up2.reset(new int(30)); // 释放原有内存,指向新内存
cout << *up2 << endl; // 输出:30
up2.reset(); // 释放内存,up2变为空
if (up2 == nullptr) {
cout << "up2已为空" << endl;
}
return 0;
}
unique_ptr);vector> );std::shared_ptr 是共享所有权智能指针,核心特性:
shared_ptr 可指向同一块内存;unique_ptr(需维护引用计数)。
#include <memory>
#include <iostream>
using namespace std;
int main() {
// 1. 创建shared_ptr(推荐使用make_shared)
shared_ptr<int> sp1(new int(10));
shared_ptr<int> sp2 = make_shared<int>(20); // 更高效,一次分配内存
// 2. 查看引用计数
cout << "sp1引用计数:" << sp1.use_count() << endl; // 输出:1
cout << "sp2引用计数:" << sp2.use_count() << endl; // 输出:1
// 3. 拷贝:引用计数增加
shared_ptr<int> sp3 = sp1;
cout << "sp1引用计数:" << sp1.use_count() << endl; // 输出:2
cout << "sp3引用计数:" << sp3.use_count() << endl; // 输出:2
// 4. 修改数据(所有共享指针都可见)
*sp3 = 100;
cout << *sp1 << endl; // 输出:100
// 5. 重置指针:引用计数减少
sp1.reset();
cout << "sp1引用计数:" << (sp1 ? sp1.use_count() : 0) << endl; // 输出:0
cout << "sp3引用计数:" << sp3.use_count() << endl; // 输出:1
// 6. 最后一个指针析构时,释放内存
sp3.reset();
cout << "sp3引用计数:" << (sp3 ? sp3.use_count() : 0) << endl; // 输出:0
return 0;
}
shared_ptr 内部维护两个指针:
shared_ptr,会导致多次释放内存:int* p = new int(10); shared_ptr sp1(p); shared_ptr sp2(p); → 双重释放!
std::weak_ptr 是为解决 shared_ptr 循环引用 问题而生的弱引用指针:
shared_ptr 管理的内存;shared_ptr(lock() 方法)才能访问数据;expired() 方法)。
// 循环引用导致内存泄漏
struct Node {
shared_ptr<Node> next; // 共享指针指向另一个节点
~Node() { cout << "Node析构" << endl; }
};
void cycleRef() {
shared_ptr<Node> n1 = make_shared<Node>();
shared_ptr<Node> n2 = make_shared<Node>();
n1->next = n2; // n2计数+1 → 2
n2->next = n1; // n1计数+1 → 2
// 函数结束,n1/n2析构 → 计数各减1 → 均为1
// 计数不为0,内存未释放 → 泄漏
}
#include <memory>
#include <iostream>
using namespace std;
// 修复循环引用:将其中一个指针改为weak_ptr
struct Node {
weak_ptr<Node> next; // 弱引用,不增加计数
~Node() { cout << "Node析构" << endl; }
};
void fixCycleRef() {
shared_ptr<Node> n1 = make_shared<Node>();
shared_ptr<Node> n2 = make_shared<Node>();
n1->next = n2; // weak_ptr,n2计数仍为1
n2->next = n1; // weak_ptr,n1计数仍为1
// 函数结束,n1/n2析构 → 计数减为0 → 内存释放,析构函数执行
}
// weak_ptr核心用法
void weakPtrUsage() {
shared_ptr<int> sp = make_shared<int>(100);
weak_ptr<int> wp = sp; // 弱引用,sp计数仍为1
cout << "wp是否过期:" << boolalpha << wp.expired() << endl; // 输出:false
// 转换为shared_ptr访问数据
if (shared_ptr<int> sp2 = wp.lock()) {
cout << *sp2 << endl; // 输出:100
cout << "sp计数:" << sp.use_count() << endl; // 输出:2
}
// 释放sp,内存失效
sp.reset();
cout << "wp是否过期:" << boolalpha << wp.expired() << endl; // 输出:true
// lock()返回空指针
if (shared_ptr<int> sp2 = wp.lock()) {
cout << *sp2 << endl;
} else {
cout << "内存已释放" << endl; // 输出:内存已释放
}
}
int main() {
fixCycleRef(); // 输出两次Node析构
weakPtrUsage();
return 0;
}
std::auto_ptr 是 C++98 引入的第一个智能指针,设计目标是实现独占式内存管理,但存在严重设计缺陷,C++11 已弃用,C++17 正式移除。
// auto_ptr 基本用法(不推荐使用)
#include <memory>
#include <iostream>
using namespace std;
int main() {
auto_ptr<int> ap(new int(10));
cout << *ap << endl; // 输出:10
// 拷贝赋值:所有权转移(隐藏的陷阱)
auto_ptr<int> ap2 = ap;
// ap已失去所有权,变为空
if (ap.get() == nullptr) {
cout << "ap已失去所有权" << endl;
}
cout << *ap2 << endl; // 输出:10
return 0;
}
| 缺陷点 | 具体问题 | 危害 |
|---|---|---|
| 拷贝/赋值语义错误 | 拷贝/赋值时转移所有权,而非深拷贝,原指针变为空 | 原指针解引用会导致程序崩溃,逻辑难以追踪 |
| 不支持数组 | 析构时调用 delete 而非 delete[] |
管理数组时导致内存泄漏/未定义行为 |
| 无法用于容器 | 容器拷贝/排序时会触发所有权转移,导致容器内指针失效 | vector<auto_ptr<int>> 等用法会引发崩溃 |
| 无移动语义支持 | C++11 引入移动语义后,auto_ptr 的拷贝语义与标准冲突 | 不符合现代C++设计理念,易与其他智能指针混淆 |
std::unique_ptr(禁用拷贝,显式移动);std::shared_ptr(引用计数,安全拷贝);std::unique_ptr<T[]>(专门支持数组析构)。
unique_ptr 返回,避免调用方忘记释放(自动转移所有权);vector<unique_ptr<Obj>> 替代 vector<Obj*>,自动释放容器内对象;shared_ptr 配合互斥锁,安全管理多线程共享的动态资源;weak_ptr 打破 shared_ptr 循环引用;make_unique/make_shared 比直接 new 更安全,避免内存泄漏;delete,引发未定义行为;std::move 转移所有权,避免隐式拷贝;shared_ptr fp(fopen("test.txt", "r"), fclose); ;unique_ptr:独占所有权,轻量高效,是裸指针的首选替代;shared_ptr:共享所有权,通过引用计数管理,适用于多指针共享资源;weak_ptr:弱引用,解决 shared_ptr 循环引用问题,不影响内存释放;auto_ptr:设计缺陷,已被弃用,禁止在新项目中使用;unique_ptr,需共享时用 shared_ptr,循环引用时加 weak_ptr;智能指针是 C++ 内存管理的核心工具,掌握其用法和原理,能大幅提升代码的健壮性和可维护性,是现代 C++ 开发的必备技能。