智能指针核心背景与概念
原始指针的痛点
C++ 中使用原始指针(raw pointer)管理动态资源(堆内存、文件描述符、互斥锁、数据库连接等)时,必须手动调用释放逻辑(如delete、close等)。但以下场景会导致资源泄漏:
- 函数提前
return,跳过释放语句; - 程序抛出异常,释放语句未执行;
- 程序员手动遗漏释放操作。
示例:原始指针的资源泄漏风险
cpp
#include <iostream>
using namespace std;
class A {
int size;
char* p;
public:
/**
* @brief 构造函数:分配堆内存
* @param s 内存大小,默认值1
* @note 动态分配char类型数组,资源需在析构中释放
*/
A(int s = 1) : size(s) {
cout << "A构造:分配" << s << "字节内存" << endl;
p = new char[s];
}
/**
* @brief 析构函数:释放堆内存
* @note 必须调用delete[],否则堆数组内存泄漏;若未执行析构,资源永久占用
*/
~A() {
cout << "A析构:释放" << size << "字节内存" << endl;
delete[] p;
}
/**
* @brief 调整内存大小
* @param newSize 新的内存大小
* @note 先释放旧内存,再分配新内存,避免内存泄漏
*/
void resize(int newSize) {
size = newSize;
delete[] p;
p = new char[size];
}
/**
* @brief 打印内存大小信息
* @return void 无返回值
*/
void info() { cout << "当前内存大小:" << size << endl; }
};
/**
* @brief 原始指针管理资源的风险示例
* @return void 无返回值
* @note 若中间抛出异常/提前return,delete p不会执行,导致内存泄漏
*/
void someFunction() {
A* p = new A(100); // 分配资源
// ... 业务逻辑,可能抛出异常/提前return
delete p; // 手动释放,无法保证一定执行
}
智能指针核心概念

智能指针是 C++ 标准库提供的类模板,核心特性:
- 本质是栈对象(而非指针),封装原始指针;
- 重载
operator->和operator*,支持像普通指针一样访问资源; - 栈对象离开作用域时自动调用析构函数,释放封装的资源;
- 核心目的:自动化资源管理,杜绝资源泄漏。
废弃的 auto_ptr(C++17 移除)
auto_ptr 基本用法
auto_ptr 是早期独占式智能指针,能自动释放资源,但设计存在缺陷。
cpp
#include <iostream>
#include <memory>
// 智能指针头文件using namespace std;
/**
* @brief auto_ptr基本使用示例
* @return void 无返回值
* @note auto_ptr是栈对象,函数退出时自动析构,调用A的析构释放资源
*/
void autoPtrDemo() {
// 封装原始指针,auto_ptr接管A对象的资源管理权
auto_ptr<A> ap(new A(20));
// 重载operator->,等价于(*ap).info()
ap->info(); // 输出:当前内存大小:20
ap->resize(100);
ap->info(); // 输出:当前内存大小:100
} // ap离开作用域,析构时调用A的析构,释放内存
int main() {
autoPtrDemo();
return 0;
}
// 执行结果:
// A构造:分配20字节内存
// 当前内存大小:20
// 当前内存大小:100
// A析构:释放100字节内存
auto_ptr 的致命缺陷
auto_ptr 的拷贝构造 / 赋值运算符会转移资源所有权,原 auto_ptr 失效(内部原始指针置空),语法允许操作但逻辑错误,极易导致程序崩溃。
cpp
/**
* @brief auto_ptr拷贝/赋值的缺陷示例
* @return void 无返回值
* @note 拷贝构造/赋值后,原智能指针失效,解引用会触发空指针异常
*/
void autoPtrProblem() {
auto_ptr<A> sp1(new A(20)); // sp1接管资源,引用A对象
auto_ptr<A> sp2(sp1); // 拷贝构造:sp1的所有权转移给sp2,sp1内部指针置空
// sp1->info() 等价于 (*sp1).info(),但sp1已无资源,解引用空指针触发崩溃
// sp1->info(); // 运行时异常/程序中断
auto_ptr<A> sp3;
sp3 = sp2; // 赋值操作:sp2的所有权转移给sp3,sp2内部指针置空
// sp2->info(); // 同样触发崩溃
sp3->resize(666);
sp3->info(); // 正常执行,输出:当前内存大小:666
}
auto_ptr 被废弃的原因
- 语法支持拷贝 / 赋值,但逻辑上转移所有权,原指针失效,易踩坑;
- C++11 引入
unique_ptr替代 auto_ptr,C++17 正式移除 auto_ptr。
shared_ptr(共享式智能指针)
shared_ptr 核心原理
shared_ptr 是 C++11 引入的共享所有权智能指针 ,核心通过引用计数实现:
- 每个 shared_ptr 管理的资源对应一个「引用计数」(全局 / 动态分配的计数器);
- 新增 shared_ptr(拷贝 / 赋值),计数 + 1;
- shared_ptr 析构时,计数 - 1;
- 计数减至 0 时,调用资源的析构函数,释放资源。
shared_ptr 基本用法
cpp
#include <iostream>
#include <memory>
using namespace std;
/**
* @brief shared_ptr基本使用示例
* @return void 无返回值
* @note 多个shared_ptr共享同一资源,引用计数保证资源仅释放一次
*/
void sharedPtrDemo() {
// sp1接管A对象,引用计数=1
shared_ptr<A> sp1(new A(30));
// 拷贝构造:引用计数=2
shared_ptr<A> sp2(sp1);
sp1->info(); // 正常,输出:当前内存大小:30
sp2->info(); // 正常,输出:当前内存大小:30
shared_ptr<A> sp3;
sp3 = sp1; // 赋值:引用计数=3
sp1->info(); // 正常,输出:当前内存大小:30
sp2->info(); // 正常,输出:当前内存大小:30
sp3->info(); // 正常,输出:当前内存大小:30
// 修改资源,所有共享指针都可见
sp1->resize(100);
sp1->info(); // 输出:当前内存大小:100
sp2->info(); // 输出:当前内存大小:100
sp3->info(); // 输出:当前内存大小:100
} // 函数退出,sp3、sp2、sp1依次析构:
// sp3析构 → 计数=2;sp2析构 → 计数=1;sp1析构 → 计数=0 → 释放A对象
int main() {
sharedPtrDemo();
return 0;
}
// 执行结果:
// A构造:分配30字节内存
// 当前内存大小:30
// 当前内存大小:30
// 当前内存大小:30
// 当前内存大小:30
// 当前内存大小:30
// 当前内存大小:100
// 当前内存大小:100
// 当前内存大小:100
// A析构:释放100字节内存
shared_ptr 的常见问题与解决
问题 1:重复关联原始指针(重复释放)
多个 shared_ptr 直接关联同一个原始指针,会创建多个独立的引用计数,导致资源被多次释放(double free)。
cpp
/**
* @brief shared_ptr重复关联原始指针的错误示例
* @return void 无返回值
* @note sp1和sp2各自创建引用计数,析构时均尝试释放p,导致double free
*/
void sharedPtrDoubleFree() {
A* p = new A(20); // 原始指针
shared_ptr<A> sp1(p); // sp1关联p,引用计数=1
shared_ptr<A> sp2(p); // sp2关联p,创建新的引用计数=1(而非复用sp1的计数)
} // sp2析构 → 计数=0 → 释放p;sp1析构 → 计数=0 → 再次释放p → 崩溃
// 执行结果:
// A构造:分配20字节内存
// A析构:释放20字节内存
// A析构:释放20字节内存
// free(): double free detected in tcache 2
// Aborted (core dumped)
解决方法:通过拷贝 / 赋值创建多个 shared_ptr,而非直接关联同一原始指针。
问题 2:循环引用(内存泄漏)

两个(或多个)shared_ptr 互相引用,导致引用计数无法减至 0,资源永久泄漏。
cpp
#include <iostream>
#include <memory>
using namespace std;
// 前向声明
class B;
class A {
public:
int size;
char* p;
shared_ptr<B> spb; // 引用B的shared_ptr
/**
* @brief A类构造函数
* @param s 内存大小
* @note 分配堆内存,初始化shared_ptr<B>为空
*/
A(int s) : size(s) {
cout << "A构造:分配" << s << "字节内存" << endl;
p = new char[s];
}
/**
* @brief A类析构函数
* @note 释放堆内存,若循环引用则不会执行
*/
~A() {
cout << "A析构:释放" << size << "字节内存" << endl;
delete[] p;
}
/**
* @brief 打印A的大小信息
* @return void 无返回值
*/
void info() { cout << "A的大小:" << size << endl; }
};
class B {
public:
int size;
char* p;
shared_ptr<A> spa; // 引用A的shared_ptr
/**
* @brief B类构造函数
* @param s 内存大小
* @note 分配堆内存,初始化shared_ptr<A>为空
*/
B(int s) : size(s) {
cout << "B构造:分配" << s << "字节内存" << endl;
p = new char[s];
}
/**
* @brief B类析构函数
* @note 释放堆内存,若循环引用则不会执行
*/
~B() {
cout << "B析构:释放" << size << "字节内存" << endl;
delete[] p;
}
/**
* @brief 打印B的大小信息
* @return void 无返回值
*/
void info() { cout << "B的大小:" << size << endl; }
};
/**
* @brief shared_ptr循环引用的错误示例
* @return void 无返回值
* @note spa和spb互相引用,计数无法归0,析构不执行,内存泄漏
*/
void sharedPtrCycle() {
shared_ptr<A> spa(new A(100)); // spa计数=1
shared_ptr<B> spb(new B(200)); // spb计数=1
spa->spb = spb; // spa的spb引用spb → spb计数=2
spb->spa = spa; // spb的spa引用spa → spa计数=2
spa->spb->info(); // 输出:B的大小:200
spb->spa->info(); // 输出:A的大小:100
} // 函数退出:
// spb析构 → 计数=1(spa->spb仍引用);spa析构 → 计数=1(spb->spa仍引用);
// 计数未归0,A和B的析构不执行 → 内存泄漏
**解决方法:使用 weak_ptr(弱引用智能指针)**weak_ptr 是 shared_ptr 的「助手」,核心特性:

- 只能通过 shared_ptr / 其他 weak_ptr 构造,不接管资源所有权;
- 构造 / 析构不改变引用计数;
- 无法直接访问资源,需通过
lock()获取 shared_ptr 后访问; - 核心接口:
use_count():返回关联资源的引用计数;expired():判断关联资源是否已释放(计数 = 0);lock():返回指向资源的 shared_ptr(若资源未释放),否则返回空 shared_ptr。
cpp
#include <iostream>
#include <memory>
using namespace std;
// 前向声明
class B;
class A {
public:
int size;
char* p;
weak_ptr<B> spb; // 改为weak_ptr,不增加计数
A(int s) : size(s) {
cout << "A构造:分配" << s << "字节内存" << endl;
p = new char[s];
}
~A() {
cout << "A析构:释放" << size << "字节内存" << endl;
delete[] p;
}
void info() { cout << "A的大小:" << size << endl; }
};
class B {
public:
int size;
char* p;
weak_ptr<A> spa; // 改为weak_ptr,不增加计数
B(int s) : size(s) {
cout << "B构造:分配" << s << "字节内存" << endl;
p = new char[s];
}
~B() {
cout << "B析构:释放" << size << "字节内存" << endl;
delete[] p;
}
void info() { cout << "B的大小:" << size << endl; }
};
/**
* @brief weak_ptr解决循环引用的示例
* @return void 无返回值
* @note weak_ptr不增加引用计数,函数退出时计数归0,资源正常释放
*/
void weakPtrSolveCycle() {
shared_ptr<A> spa(new A(100)); // spa计数=1
shared_ptr<B> spb(new B(200)); // spb计数=1
spa->spb = spb; // weak_ptr赋值,spb计数仍=1
spb->spa = spa; // weak_ptr赋值,spa计数仍=1
// 检查资源是否有效,再通过lock()获取shared_ptr访问资源
if (!spa->spb.expired()) {
shared_ptr<B> tmp_b = spa->spb.lock(); // tmp_b计数=2
tmp_b->info(); // 输出:B的大小:200
} // tmp_b析构 → spb计数=1
if (!spb->spa.expired()) {
shared_ptr<A> tmp_a = spb->spa.lock(); // tmp_a计数=2
tmp_a->info(); // 输出:A的大小:100
} // tmp_a析构 → spa计数=1
} // 函数退出:
// spb析构 → 计数=0 → 释放B;spa析构 → 计数=0 → 释放A;
// 无内存泄漏
int main() {
weakPtrSolveCycle();
return 0;
}
// 执行结果:
// A构造:分配100字节内存
// B构造:分配200字节内存
// B的大小:200
// A的大小:100
// B析构:释放200字节内存
// A析构:释放100字节内存
问题 3:多线程访问共享资源(线程安全)
shared_ptr 本身是线程安全的(引用计数的增减是原子操作),但共享资源的访问不是线程安全的,多线程并发读写会导致数据竞争。
cpp
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
#include <unistd.h>
#include <cstdlib>
#include <ctime>
using namespace std;
class A {
public:
int x;
/**
* @brief A类构造函数
* @param x 初始化值,默认0
* @note 初始化x,无动态资源
*/
A(int x = 0) : x(x) {}
/**
* @brief 设置x的值
* @param x 新值
* @return void 无返回值
*/
void setX(int x) { this->x = x; }
};
mutex m; // 全局互斥锁,保护共享资源访问
/**
* @brief 线程1:判断x是否为偶数并打印
* @param sp 共享的shared_ptr<A>
* @return void 无返回值
* @note 不加锁会导致读取x时被线程2修改,输出错误结果
*/
void routine1(shared_ptr<A> sp) {
while (1) {
m.lock(); // 加锁,保证读取x的原子性
if (sp->x % 2 == 0) {
cout << sp->x << "是偶数" << endl;
}
m.unlock(); // 解锁
usleep(10 * 1000); // 休眠10ms,降低CPU占用
}
}
/**
* @brief 线程2:随机修改x的值
* @param sp 共享的shared_ptr<A>
* @return void 无返回值
* @note 加锁,保证修改x的原子性
*/
void routine2(shared_ptr<A> sp) {
srand(time(NULL)); // 初始化随机数种子
while (1) {
m.lock(); // 加锁
sp->setX(rand() % 1000); // 随机设置0-999
m.unlock(); // 解锁
}
}
int main() {
shared_ptr<A> sp1(new A); // 共享资源
shared_ptr<A> sp2(sp1);
// 创建线程,传递shared_ptr(拷贝,计数+1)
thread t1(routine1, sp1);
thread t2(routine2, sp2);
t1.detach(); // 分离线程,后台运行
t2.detach();
pthread_exit(NULL); // 主线程退出,子线程继续
return 0;
}
// 加锁后执行结果(无错误):
// 396是偶数
// 596是偶数
// 18是偶数
// 406是偶数
unique_ptr(独占式智能指针)
unique_ptr 核心特性
unique_ptr 是 C++11 引入的独占式智能指针,替代废弃的 auto_ptr,核心特性:
- 独占资源所有权,同一时间只有一个 unique_ptr 能管理某个资源;
- 禁止拷贝构造和赋值运算符(语法层面禁用,避免 auto_ptr 的坑);
- 支持移动语义 (
std::move),将资源所有权转移给另一个 unique_ptr; - 效率与原始指针接近,无引用计数开销。
unique_ptr 基本用法
cpp
#include <iostream>
#include <memory>
using namespace std;
/**
* @brief unique_ptr基本使用示例
* @return void 无返回值
* @note unique_ptr禁止拷贝/赋值,仅允许移动语义,独占资源
*/
void uniquePtrDemo() {
// up独占A对象的资源
unique_ptr<A> up(new A(20));
up->info(); // 输出:当前内存大小:20
up->resize(200);
up->info(); // 输出:当前内存大小:200
// 错误:unique_ptr禁用拷贝构造
// unique_ptr<A> up2(up);
// 错误:unique_ptr禁用赋值运算符
// unique_ptr<A> up3;
// up3 = up;
// 正确:移动语义,up的所有权转移给up4,up变为空
unique_ptr<A> up4 = move(up);
// up->info(); // up已空,调用崩溃
up4->info(); // 输出:当前内存大小:200
} // up4离开作用域,析构释放A对象
int main() {
uniquePtrDemo();
return 0;
}
// 执行结果:
// A构造:分配20字节内存
// 当前内存大小:20
// 当前内存大小:200
// 当前内存大小:200
// A析构:释放200字节内存
手动实现 myUniquePtr(模拟 unique_ptr)
cpp
#include <iostream>
#include <utility> // std::move
using namespace std;
/**
* @brief 自定义独占式智能指针类模板
* @tparam T 资源类型
* @note 模拟unique_ptr核心特性:禁止拷贝/赋值,支持移动语义,自动释放资源
*/
template <typename T>
class myUniquePtr {
private:
T* ptr; // 封装的原始指针
// 禁用拷贝构造(私有+删除,语法层面禁止)
myUniquePtr(const myUniquePtr&) = delete;
// 禁用赋值运算符(私有+删除)
myUniquePtr& operator=(const myUniquePtr&) = delete;
public:
/**
* @brief 构造函数:接管原始指针
* @param p 原始指针,默认nullptr
* @note 接管资源,后续由myUniquePtr管理释放
*/
explicit myUniquePtr(T* p = nullptr) : ptr(p) {}
/**
* @brief 移动构造函数:转移资源所有权
* @param other 待移动的myUniquePtr
* @note 接管other的资源,other的指针置空
*/
myUniquePtr(myUniquePtr&& other) noexcept {
ptr = other.ptr;
other.ptr = nullptr; // 原指针置空,避免重复释放
}
/**
* @brief 移动赋值运算符:转移资源所有权
* @param other 待移动的myUniquePtr
* @return myUniquePtr& 自身引用
* @note 先释放自身资源,再接管other的资源,other置空
*/
myUniquePtr& operator=(myUniquePtr&& other) noexcept {
if (this != &other) { // 避免自赋值
delete ptr; // 释放当前资源
ptr = other.ptr; // 接管other的资源
other.ptr = nullptr; // other置空
}
return *this;
}
/**
* @brief 析构函数:释放资源
* @note 自动调用,释放封装的原始指针指向的资源
*/
~myUniquePtr() {
delete ptr; // 释放资源(若ptr非空)
}
/**
* @brief 重载operator->:支持指针式访问
* @return T* 原始指针
* @note 等价于(*this).ptr,用于访问资源的成员
*/
T* operator->() const {
return ptr;
}
/**
* @brief 重载operator*:解引用访问资源
* @return T& 资源的引用
* @note 用于直接访问资源对象
*/
T& operator*() const {
return *ptr;
}
/**
* @brief 获取原始指针
* @return T* 封装的原始指针
* @note 谨慎使用,避免手动管理资源导致冲突
*/
T* get() const {
return ptr;
}
/**
* @brief 释放资源所有权(不释放资源)
* @return T* 原始指针
* @note 调用后myUniquePtr不再管理该资源,需手动释放
*/
T* release() {
T* tmp = ptr;
ptr = nullptr;
return tmp;
}
/**
* @brief 重置资源
* @param p 新的原始指针,默认nullptr
* @return void 无返回值
* @note 先释放当前资源,再接管新资源
*/
void reset(T* p = nullptr) {
delete ptr;
ptr = p;
}
};
/**
* @brief 测试自定义myUniquePtr
* @return void 无返回值
* @note 验证独占性、移动语义、自动释放
*/
void testMyUniquePtr() {
myUniquePtr<A> up(new A(50));
up->info(); // 输出:当前内存大小:50
// 移动构造
myUniquePtr<A> up2(move(up));
// up->info(); // up已空,崩溃
up2->resize(150);
up2->info(); // 输出:当前内存大小:150
// 移动赋值
myUniquePtr<A> up3;
up3 = move(up2);
up3->info(); // 输出:当前内存大小:150
} // up3析构 → 释放A对象
int main() {
testMyUniquePtr();
return 0;
}
// 执行结果:
// A构造:分配50字节内存
// 当前内存大小:50
// 当前内存大小:150
// 当前内存大小:150
// A析构:释放150字节内存
智能指针选型总结
| 智能指针 | 核心特性 | 适用场景 | 注意事项 |
|---|---|---|---|
| auto_ptr | 独占式,拷贝 / 赋值转移所有权 | 已废弃,不推荐使用 | C++17 移除,易踩坑 |
| shared_ptr | 共享式,引用计数 | 多对象共享同一资源 | 避免循环引用(配合 weak_ptr)、不重复关联原始指针 |
| weak_ptr | 弱引用,不增减计数 | 解决 shared_ptr 循环引用 | 需通过 lock () 访问资源,先检查 expired () |
| unique_ptr | 独占式,禁用拷贝 / 赋值,支持移动 | 单个对象独占资源,替代 auto_ptr | 效率最高,无计数开销,仅支持移动语义 |