在 C++ 编程中,内存泄漏是长期困扰开发者的核心问题之一。尤其是在异常场景下,手动管理的动态内存常常因释放逻辑未执行而导致泄漏。智能指针作为 RAII(Resource Acquisition Is Initialization)思想的经典实现,通过对象生命周期自动管理资源,彻底解决了这一痛点。本文将从使用场景出发,深入剖析智能指针的设计原理、标准库实现细节,并附上完整可运行的自定义实现代码,最后通过可视化插图,帮助大家直观理解核心概念。
一、为什么需要智能指针?------ 异常场景下的内存泄漏问题
先看一个典型的反例:当代码中存在异常抛出时,手动释放内存的逻辑可能失效,导致内存泄漏。
double Divide(int a, int b) {
if (b == 0)
throw "Divide by zero condition!"; // 除0抛出异常
return (double)a / (double)b;
}
void Func() {
int* array1 = new int[10];
int* array2 = new int[10]; // 若此处抛异常,array1已无法释放
try {
int len, time;
cin >> len >> time;
cout << Divide(len, time) << endl;
}
catch (...) {
// 捕获异常后释放内存,但无法覆盖array2初始化时的异常
delete[] array1;
delete[] array2;
throw;
}
delete[] array1;
delete[] array2;
}
上述代码存在两个致命问题:
- 若
array2的new操作抛出异常,array1已分配的内存无法释放; - 异常处理逻辑冗余,多层
new需要嵌套多个try-catch。
而智能指针能完美解决这些问题 ------ 它将资源管理与对象生命周期绑定,无论程序正常执行还是异常退出,都会在对象析构时自动释放资源。
二、智能指针的核心设计思想:RAII
RAII(资源获取即初始化)是智能指针的灵魂,其核心逻辑如下:
- 资源获取:在智能指针对象构造时,获取动态内存、文件句柄等资源;
- 资源持有:智能指针对象生命周期内,资源始终有效且唯一被管理;
- 资源释放:智能指针对象析构时,自动释放持有的资源。
为了让智能指针像普通指针一样使用,还需要重载operator*、operator->等运算符,模拟指针的行为。
三、C++ 标准库智能指针详解
C++ 标准库(<memory>头文件)提供了 4 种智能指针,各自适用于不同场景,核心特性对比如下:
| 智能指针 | 核心特性 | 适用场景 | 注意事项 |
|---|---|---|---|
| auto_ptr | 拷贝时转移资源管理权 | 已废弃(C++11 后不推荐) | 被拷贝对象悬空,易触发崩溃 |
| unique_ptr | 独占所有权,不支持拷贝 | 无需共享资源的场景 | 支持移动语义(std::move) |
| shared_ptr | 共享所有权,支持拷贝 | 需多对象共享资源的场景 | 底层通过引用计数实现,需避免循环引用 |
| weak_ptr | 不持有所有权,仅观察 | 解决 shared_ptr 循环引用 | 不能直接访问资源,需通过lock()转换 |
3.1 自定义智能指针实现(带详细注释)
下面附上完整的自定义智能指针实现代码,包含auto_ptr、unique_ptr、shared_ptr和weak_ptr,所有关键逻辑均添加注释:
1. auto_ptr 实现
#pragma once
#include <iostream>
using namespace std;
namespace yzq {
template<class T>
class auto_ptr {
public:
// 构造函数:获取资源(RAII第一步)
auto_ptr(T* ptr)
:_ptr(ptr)
{ }
// 拷贝构造:转移资源管理权(auto_ptr的核心缺陷)
auto_ptr(auto_ptr<T>& sp) {
_ptr = sp._ptr; // 当前对象接管资源
sp._ptr = nullptr; // 原对象置空,变为"悬空指针"
}
// 赋值运算符重载:先释放当前资源,再转移目标资源
auto_ptr<T>& operator=(auto_ptr<T>& ap) {
// 1. 避免自赋值
if (this != &ap) {
// 2. 释放当前对象持有的资源
if (_ptr)
delete _ptr; // 析构单个对象(不支持数组,需注意)
// 3. 转移目标对象的资源
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
// 析构函数:自动释放资源(RAII第三步)
~auto_ptr() {
if (_ptr) {
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 重载指针运算符,模拟普通指针行为
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
private:
T* _ptr; // 指向管理的资源
};
}
2. unique_ptr 实现
#pragma once
#include <iostream>
#include <algorithm>
using namespace std;
namespace yzq {
template<class T>
class unique_ptr {
public:
// 构造函数:获取资源
unique_ptr(T* ptr)
:_ptr(ptr)
{ }
// 禁用拷贝构造:直接delete,防止隐式生成
unique_ptr(unique_ptr<T>& sp) = delete;
// 禁用赋值运算符:防止资源共享
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
// 移动构造:转移资源所有权(显式移动,避免意外拷贝)
unique_ptr(unique_ptr<T>&& sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr; // 原对象置空
}
// 移动赋值运算符:转移资源所有权
unique_ptr<T>& operator=(unique_ptr<T>&& sp) {
// 释放当前资源
delete _ptr;
// 转移目标资源
_ptr = sp._ptr;
sp._ptr = nullptr;
return *this;
}
// 析构函数:自动释放资源
~unique_ptr() {
if (_ptr) {
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
// 重载指针运算符
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
private:
T* _ptr; // 指向管理的资源
};
}
3. shared_ptr 实现
#pragma once
#include <iostream>
#include <functional>
using namespace std;
namespace yzq {
template<class T>
class shared_ptr {
public:
// 释放资源的核心逻辑:引用计数为0时销毁资源
void release() {
// 引用计数存在且减为0时,释放资源和计数
if (_pcount && --(*_pcount) == 0) {
_del(_ptr); // 调用自定义删除器
delete _pcount; // 释放引用计数
_ptr = nullptr;
_pcount = nullptr;
}
}
// 默认构造函数:可选传入资源指针
explicit shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
// 资源非空时,引用计数初始化为1
, _pcount(ptr ? new int(1) : nullptr)
{ }
// 带删除器的构造函数:支持自定义资源释放方式
template<class D>
explicit shared_ptr(T* ptr, D del)
:_ptr(ptr)
,_pcount(ptr ? new int(1) : nullptr)
,_del(del) // 存储自定义删除器
{ }
// 拷贝构造:共享资源,引用计数+1
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
,_del(sp._del)
{
++(*_pcount); // 引用计数自增
}
// 移动构造:转移资源所有权,不修改引用计数
shared_ptr(shared_ptr<T>&& sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
, _del(std::move(sp._del)) // 移动删除器
{
sp._ptr = nullptr;
sp._pcount = nullptr;
}
// 拷贝赋值:先释放当前资源,再共享目标资源
shared_ptr& operator=(const shared_ptr<T>& sp) {
// 避免自赋值
if (this != &sp) {
release(); // 释放当前资源(可能触发销毁)
_ptr = sp._ptr;
_pcount = sp._pcount;
_del = sp._del;
if (_pcount) {
(*_pcount)++; // 引用计数+1
}
}
return *this;
}
// 移动赋值:转移资源所有权
shared_ptr& operator=(shared_ptr<T>&& sp) noexcept {
if (this != &sp) {
release(); // 释放当前资源
_ptr = sp._ptr;
_pcount = sp._pcount;
_del = std::move(sp._del);
sp._ptr = nullptr;
sp._pcount = nullptr;
}
return *this;
}
// 析构函数:调用release释放资源
~shared_ptr() {
release();
}
// 获取原始指针
T* get() const {
return _ptr;
}
// 获取当前引用计数
int use_count() const {
return _pcount ? *_pcount : 0;
}
// 重载指针运算符
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
private:
T* _ptr; // 指向管理的资源
int* _pcount; // 引用计数(堆上分配,支持多对象共享)
// 默认删除器:支持自定义释放逻辑(如数组、文件句柄)
std::function<void(T*)> _del = [](T* ptr) {delete ptr; };
};
}
4. weak_ptr 实现
#pragma once
#include "shared_ptr.h" // 依赖shared_ptr
template<class T>
class weak_ptr {
public:
// 默认构造函数
weak_ptr() { }
// 从shared_ptr构造:不增加引用计数
weak_ptr(const shared_ptr<T>& sp)
: _ptr(sp.get())
{ }
// 赋值运算符:从shared_ptr赋值,不增加引用计数
weak_ptr<T>& operator=(const shared_ptr<T>& sp) {
_ptr = sp.get();
return *this;
}
// 检查资源是否已释放
bool expired() const {
return _ptr == nullptr;
}
// 转换为shared_ptr以访问资源(安全访问)
shared_ptr<T> lock() const {
return shared_ptr<T>(_ptr);
}
private:
T* _ptr = nullptr; // 仅观察资源,不持有所有权
};
3.2 关键特性实战
1. 自定义删除器(处理数组 / 文件句柄)
shared_ptr和unique_ptr支持自定义删除器,解决默认delete无法处理的场景(如数组、文件句柄):
// 数组删除器(仿函数)
template<class T>
class DeleteArray {
public:
void operator()(T* ptr) {
delete[] ptr;
}
};
// 文件句柄删除器
class Fclose {
public:
void operator()(FILE* ptr) {
cout << "fclose:" << ptr << endl;
fclose(ptr);
}
};
// 用法示例
int main() {
// 管理数组(使用仿函数删除器)
yzq::shared_ptr<Date> sp1(new Date[5], DeleteArray<Date>());
// 管理文件句柄(使用lambda删除器)
yzq::shared_ptr<FILE> sp2(fopen("test.txt", "r"), [](FILE* p) {fclose(p); });
return 0;
}
2. 解决 shared_ptr 循环引用
当两个shared_ptr互相引用时,会导致引用计数无法归零,引发内存泄漏。weak_ptr不增加引用计数,可完美解决:
struct ListNode {
int _data;
yzq::weak_ptr<ListNode> _next; // 用weak_ptr替代shared_ptr
yzq::weak_ptr<ListNode> _prev;
~ListNode() { cout << "~ListNode()" << endl; }
};
int main() {
yzq::shared_ptr<ListNode> n1(new ListNode);
yzq::shared_ptr<ListNode> n2(new ListNode);
cout << n1.use_count() << endl; // 输出1
cout << n2.use_count() << endl; // 输出1
n1->_next = n2; // weak_ptr赋值,n2引用计数不变
n2->_prev = n1; // weak_ptr赋值,n1引用计数不变
cout << n1.use_count() << endl; // 输出1
cout << n2.use_count() << endl; // 输出1
// 析构时引用计数归0,资源正常释放
return 0;
}
四、智能指针核心原理可视化

