智能指针
基本概念
问题背景
在 C++ 中,手动管理资源(内存、文件描述符、互斥锁、数据库连接等)时,容易因异常、提前返回等意外情况导致资源泄漏。例如:
class A {
int size;
char *p;
public:
A(int s=1):size(s){p = new char[s];}
~A(){delete [] p;} // 析构函数释放堆内存
};
void someFunction() {
A *p = new A(100); // 分配资源
// 若此处提前返回或抛出异常,delete p 无法执行,导致内存泄漏
...
delete p; // 手动释放资源,可靠性低
}
这种直接指向资源、无自动管理能力的指针称为原始指针(raw pointer),其核心缺陷是:无法保证资源在任意场景下被妥善释放。
智能指针核心定义
智能指针是 C++ 提供的类模板,用于自动化管理资源,核心特征:
- 本质是栈对象(局部变量),而非真正的指针;
- 内部封装了指向资源的原始指针;
- 离开作用域时,自动调用析构函数释放资源(无需手动操作);
- 重载
operator->和operator*运算符,用法与普通指针一致。
智能指针对象(栈上)
├─ 成员:原始指针 *p(指向堆/资源)
├─ 重载:operator->、operator*
└─ 析构函数:自动释放 *p 指向的资源
↓
资源(堆内存/文件描述符/互斥锁等)
智能指针分类详解
auto_ptr(C++17 已废弃)
基本用法
std::auto_ptr 是早期智能指针,核心作用是自动释放资源,需包含头文件 <memory>:
#include <memory>
#include <iostream>
using namespace std;
class A {
int size;
char *p;
public:
A(int s=1):size(s){cout<<"构造"<<endl; p = new char[s];}
~A(){cout<<"析构"<<endl; delete [] p;}
void resize(int newSize) { // 重新分配堆内存
size = newSize;
delete [] p;
p = new char[size];
}
void info(void){cout << size << endl;} // 输出大小
};
void someFunction(void) {
auto_ptr<A> ap(new A(20)); // 封装原始指针
ap->info(); // 等价于 (*ap).info(),输出 20
ap->resize(100); // 操作资源
ap->info(); // 输出 100
} // 离开作用域,ap 析构,自动释放 A 对象资源
// 执行结果:
// 构造
// 20
// 100
// 析构
核心缺陷(废弃原因)
auto_ptr 支持拷贝构造和赋值运算,但会导致原指针失去资源控制权(逻辑与语法矛盾),编译器无语法限制,极易踩坑:
void someFunction(void) {
auto_ptr<A> sp1(new A(20));
auto_ptr<A> sp2(sp1); // 拷贝构造:sp1 失去控制权
sp1->info(); // 异常!sp1 已失效
auto_ptr<A> sp3;
sp3 = sp2; // 赋值运算:sp2 失去控制权
sp2->info(); // 异常!sp2 已失效
sp3->resize(666); // 仅最后一个指针有效
sp3->info(); // 正常输出 666
}
结论:auto_ptr 仅能单独使用,禁止拷贝构造和赋值操作,因设计缺陷被 C++17 废弃,不推荐使用。
shared_ptr(共享所有权智能指针)
shared_ptr 是 C++11 引入的核心智能指针,支持多指针共享同一资源,通过引用计数解决重复释放问题,语法与逻辑自洽。
基本用法
#include <memory>
#include <iostream>
using namespace std;
void someFunction(void) {
shared_ptr<A> sp1(new A(30)); // 构造智能指针,引用计数=1
shared_ptr<A> sp2(sp1); // 拷贝构造,引用计数=2
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 依次析构,引用计数降至 0,释放资源
核心原理:引用计数
shared_ptr 内部维护一个静态引用计数器,核心逻辑:
- 当智能指针关联资源时,计数器
+1; - 智能指针析构(离开作用域)时,计数器
-1; - 计数器降至
0时,自动调用资源的析构函数释放资源。
资源(A 对象)
└─ 引用计数器:3
├─ shared_ptr sp1(关联)
├─ shared_ptr sp2(关联)
└─ shared_ptr sp3(关联)
常见问题与解决方案
问题 1:重复关联导致重复释放
直接用同一原始指针构造多个 shared_ptr,会导致计数器独立,析构时重复释放资源:
void someFunction(void) {
A *p = new A(20);
shared_ptr<A> sp1(p); // 计数器=1
shared_ptr<A> sp2(p); // 错误!计数器=1(独立计数)
// 析构时 sp1 和 sp2 均释放 p,导致双重释放崩溃
}
解决方案:多个 shared_ptr 共享资源时,通过拷贝构造或赋值操作创建,而非直接用原始指针重复构造:
shared_ptr<A> sp1(new A(20));
shared_ptr<A> sp2(sp1); // 正确:拷贝构造,计数器=2
shared_ptr<A> sp3 = sp1; // 正确:赋值,计数器=3
问题 2:循环引用导致内存泄漏
两个类互相持有对方的 shared_ptr,会导致引用计数无法降至 0,资源永久无法释放:
class B; // 前置声明
class A {
public:
shared_ptr<B> spb; // A 持有 B 的 shared_ptr
A(int s){cout<<"A 构造"<<endl;}
~A(){cout<<"A 析构"<<endl;}
};
class B {
public:
shared_ptr<A> spa; // B 持有 A 的 shared_ptr
B(int s){cout<<"B 构造"<<endl;}
~B(){cout<<"B 析构"<<endl;}
};
void someFunction(void) {
shared_ptr<A> spa(new A(100)); // 计数器=1
shared_ptr<B> spb(new B(200)); // 计数器=1
spa->spb = spb; // A 的 spb 关联 B,B 计数器=2
spb->spa = spa; // B 的 spa 关联 A,A 计数器=2
} // 析构时:spa 计数器=1,spb 计数器=1,均不释放,内存泄漏
shared_ptr spa → A 对象 → shared_ptr spb
↑
↓
shared_ptr spb → B 对象 → shared_ptr spa
解决方案:weak_ptr(弱引用指针)
weak_ptr 是 shared_ptr 的辅助指针,核心特性:
- 仅能通过
shared_ptr或其他weak_ptr构造; - 构造/析构不影响引用计数;
- 无法直接访问资源,需通过
lock()转换为shared_ptr后操作。
修改代码如下:
class B;
class A {
public:
weak_ptr<B> spb; // 改为 weak_ptr
A(int s){cout<<"A 构造"<<endl;}
~A(){cout<<"A 析构"<<endl;}
};
class B {
public:
weak_ptr<A> spa; // 改为 weak_ptr
B(int s){cout<<"B 构造"<<endl;}
~B(){cout<<"B 析构"<<endl;}
};
void someFunction(void) {
shared_ptr<A> spa(new A(100));
shared_ptr<B> spb(new B(200));
spa->spb = spb; // weak_ptr 不增加 B 的计数器(仍为1)
spb->spa = spa; // weak_ptr 不增加 A 的计数器(仍为1)
// 访问资源:先判断是否过期,再 lock() 转换
if(!spa->spb.expired()) { // expired():判断资源是否释放
spa->spb.lock()->info(); // lock():返回 shared_ptr,访问资源
}
} // 析构时:spa 计数器=0(释放 A),spb 计数器=0(释放 B),无泄漏
shared_ptr spa → A 对象 → weak_ptr spb(虚线,不计数)
↑
↓
shared_ptr spb → B 对象 → weak_ptr spa(虚线,不计数)
问题 3:多线程访问失序
shared_ptr 本身的引用计数是线程安全的,但资源的访问并非线程安全,多线程并发读写会导致数据竞争:
#include <thread>
#include <mutex>
using namespace std;
class A {
public:
int x;
A(int x=0):x(x){}
void setX(int x){this->x = x;}
};
mutex m; // 互斥锁
void routine1(shared_ptr<A> sp) {
while(1) {
m.lock(); // 加锁保证原子操作
if (sp->x % 2 == 0)
cout << sp->x << "是偶数" << endl;
m.unlock(); // 解锁
usleep(10*1000);
}
}
void routine2(shared_ptr<A> sp) {
srand(time(NULL));
while(1) {
m.lock(); // 加锁保证原子操作
sp->setX(rand()%1000);
m.unlock(); // 解锁
}
}
int main() {
shared_ptr<A> sp(new A);
thread t1(routine1, sp);
thread t2(routine2, sp);
t1.detach();
t2.detach();
pthread_exit(NULL);
}
解决方案:对共享资源的访问添加互斥锁(mutex),保证同一时间只有一个线程操作资源。
unique_ptr(独占所有权智能指针)
unique_ptr 是 C++11 引入的轻量级智能指针,核心特性:资源独占,禁止拷贝构造和赋值操作,性能优于 shared_ptr。
基本用法
#include <memory>
#include <iostream>
using namespace std;
void someFunction(void) {
unique_ptr<A> up(new A(20)); // 独占资源
up->info(); // 正常输出 20
up->resize(200);
up->info(); // 正常输出 200
// 错误!禁止拷贝构造
// unique_ptr<A> up2(up);
// 错误!禁止赋值操作
// unique_ptr<A> up3; up3 = up;
} // 离开作用域,up 析构,释放资源
核心原理:屏蔽拷贝与赋值
unique_ptr 通过 = delete 显式删除拷贝构造和赋值运算符,从语法上禁止资源共享:
template <typename T>
class myUniquePtr {
T *p;
public:
myUniquePtr(T *p=nullptr):p(p){}
~myUniquePtr(){delete p;} // 析构释放资源
// 显式删除拷贝构造和赋值运算符
myUniquePtr(const myUniquePtr &r) = delete;
myUniquePtr &operator=(const myUniquePtr &r) = delete;
// 重载运算符,支持指针用法
T *operator->(){return p;}
T &operator*(){return *p;}
// 辅助接口
T *release() { // 释放所有权,返回原始指针
T *temp = p;
p = nullptr;
return temp;
}
void reset(T *newP=nullptr) { // 重置资源
delete p;
p = newP;
}
};
拓展:移动语义(C++11+)
unique_ptr 虽禁止拷贝,但支持移动语义(std::move),可将资源所有权转移给另一个 unique_ptr:
void someFunction(void) {
unique_ptr<A> up1(new A(20));
unique_ptr<A> up2 = move(up1); // 移动语义:up1 失去所有权,up2 独占
up2->info(); // 正常输出 20
// up1->info(); 错误!up1 已失效
}
拓展(新增)
智能指针的选择策略
| 场景 | 推荐智能指针 | 核心原因 |
|---|---|---|
| 资源独占,无需共享 | unique_ptr | 性能最优,语法禁止共享,无引用计数开销 |
| 资源需多指针共享 | shared_ptr + weak_ptr | 支持共享所有权,weak_ptr 解决循环引用 |
| 兼容旧代码(C++17 前) | 无(避免 auto_ptr) | auto_ptr 设计缺陷,已废弃 |
进阶技巧
使用 make_shared 构造 shared_ptr(推荐)
std::make_shared 是构造 shared_ptr 的更安全方式,避免原始指针暴露,且内存分配更高效(一次分配资源和计数器):
// 推荐:make_shared 构造
shared_ptr<A> sp = make_shared<A>(20);
// 不推荐:直接用原始指针构造
shared_ptr<A> sp(new A(20));
自定义删除器
默认情况下,智能指针使用 delete/delete[] 释放资源,可自定义删除器处理特殊资源(如数组、文件描述符):
// 示例:管理数组(默认 delete 不适用数组,需自定义删除器)
shared_ptr<int> sp(new int[10], [](int *p){delete[] p;});
// 示例:管理文件描述符
#include <fcntl.h>
#include <unistd.h>
shared_ptr<int> fd_ptr(new int(open("test.txt", O_RDONLY)),
[](int *fd){close(*fd); delete fd;});
weak_ptr 的其他接口
use_count():返回关联资源的引用计数(仅作参考,非原子操作);lock():若资源未释放,返回非空shared_ptr;否则返回空shared_ptr;reset():解除与资源的关联。
线程安全性补充
shared_ptr的引用计数是线程安全的(原子操作);shared_ptr指向的资源并非线程安全,需手动加锁保护;unique_ptr无共享场景,线程安全取决于资源的访问方式。

浙公网安备 33010602011771号