C++智能指针详解:unique_ptr、shared_ptr与weak_ptr用法及避坑指南
C++中的智能指针通过RAII机制自动管理堆内存,有效避免手动malloc/new带来的泄漏风险。本文详解unique_ptr、shared_ptr和weak_ptr的独占、共享及解决循环依赖的原理,提供标准创建与传递代码,并指出常见陷阱与修正方案。
智能指针概述
智能指针是对原始裸指针的类封装,核心目标是实现资源的自动释放。其优点在于无需开发者手动调用delete,从而杜绝了因路径分支或异常导致未释放资源的情况;缺点则是相比裸指针使用稍显繁琐。它主要解决的是对象的所有权转移、生命周期管理及共享访问问题。
一、 unique_ptr:独占所有权指针
unique_ptr是C++11引入的最严格的智能指针,遵循“唯一所有者”原则,即同一时刻某块内存只能由一个unique_ptr管理。它具有零开销抽象的特性。
核心特点
- 独占性:禁止拷贝构造与赋值操作。若尝试让两个unique_ptr指向同一块内存,编译器虽可能编译通过(如传入旧裸指针),但运行时会触发双重释放错误。
- 自动释放:当unique_ptr离开作用域时,析构函数会自动delete指向的对象。若需跨作用域保存地址,应使用裸指针变量提前获取get()的地址。
- 移动语义:所有权的转移必须使用std::move。执行move后,原指针变为空(nullptr),所有权归属于目标指针。
常见操作代码
// 1. 基础使用与移动
#include
#include
int main() {
std::unique_ptr u_pi = std::make_unique(10);
// std::unique_ptr u_pi2 = u_pi; // 错误:不可拷贝
std::unique_ptr u_pi2 = std::move(u_pi); // 正确:转移所有权
}
创建与传递方式
创建方式:
- 传统指针转换:
std::unique_ptr<T> ptr(new T());(注意不要忘记delete原来的裸指针) - 直接new:同上。
- 推荐方式 make_unique:
auto ptr = std::make_unique<T>(args);(C++14起,更安全且性能更好)
传递方式:
- 转移所有权 (std::move):将指针传入期望独占拥有的函数。调用后原指针失效。
- 引用传递 (const &):在函数内部只读访问,不改变所有权归属,适合查看数据。
- 链式调用:支持
ptr->member()语法,方便流式处理。
// 引用传递示例
void process(const std::unique_ptr<Person>& p) {
std::cout << p->getName() << std::endl;
// p.reset(); // 错误:const对象不能修改
}
// 简单使用
std::unique_ptr<Person> up(new Person("Alice"));
up->show(); // 箭头运算符
(*up).show(); // 解引用运算符
void* addr = up.get(); // 获取裸指针地址
up.reset(); // 主动释放资源
潜在隐患
悬垂裸指针风险:如果使用已有裸指针初始化unique_ptr后,未及时将裸指针置空,会导致两处同时操作同一内存。虽然unique_ptr管理释放,但裸指针仍可非法读写。
int* raw = new int(10);
std::unique_ptr<int> smart(raw);
// raw = nullptr; // 必须立即清空!否则raw仍是野指针
*raw = 20; // 危险操作
二、 shared_ptr:共享所有权指针
shared_ptr允许多个指针共享同一块堆内存,底层通过原子计数的控制块来管理生命周期。当最后一个shared_ptr销毁时,内存才会被释放。
核心特点
- 拷贝共享:支持复制构造和赋值。每次拷贝,引用计数加1。
- 引用计数 (use_count):通过
use_count()可查询当前有多少个shared_ptr共享该对象。复制+1,销毁-1。 - 数据一致:所有共享该对象的shared_ptr看到的是同一份数据。
传递与生命周期观察
#include
#include
void pass_by_value(std::shared_ptr<int> p) {
std::cout << "In function (val): " << p.use_count() << std::endl; // 计数+1
}
void pass_by_ref(const std::shared_ptr<int>& p) {
std::cout << "In function (ref): " << p.use_count() << std::endl; // 计数不变
}
int main() {
auto sp = std::make_shared<int>(10);
std::cout << sp.use_count() << std::endl; // 输出 1
pass_by_value(sp); // 临时对象增加计数
std::cout << sp.use_count() << std::endl; // 输出恢复为 1
pass_by_ref(sp); // 引用不增加计数
return 0;
}
严重隐患:循环依赖
当两个shared_ptr互相持有对方成员(例如A的父节点B持有A,A的子节点又持有B),引用计数永远不会归零,导致内存泄漏。
// 错误示范:循环引用
struct Node {
std::shared_ptr<Node> parent;
std::shared_ptr<Node> child;
};
// A->parent = B, B->child = A; 此时A、B计数均为2,无法释放。
三、 weak_ptr:非占用型观测者
weak_ptr是为了解决shared_ptr循环依赖而设计的辅助智能指针。它不拥有对象的所有权,因此不会增加引用计数。它可以观察shared_ptr管理的对象,但不能直接使用,需先提升为shared_ptr才能访问。
解决循环依赖与基本用法
将相互持有的属性改为weak_ptr即可打破循环。使用前必须通过lock()方法尝试获取有效的shared_ptr。
#include
#include
using namespace std;
struct Person {
string name;
weak_ptr<Person> friendPtr; // 弱引用:不计入所有权
void show() {
cout << name << endl;
}
};
int main() {
shared_ptr<Person> p1 = make_shared<Person>("Alice");
shared_ptr<Person> p2 = make_shared<Person>("Bob");
p1->friendPtr = p2;
p2->friendPtr = p1;
// 安全访问检测
if(auto locked_p2 = p1->friendPtr.lock()) {
locked_p2->show(); // 只有当p2存在时才安全访问
} else {
cout << "Friend pointer is expired." << endl;
}
return 0;
}
注意事项
调用lock()时,如果原始的shared_ptr已经销毁,返回的将是空的shared_ptr,因此务必检查其有效性后再进行解引用。
总结
在现代C++开发中,应尽量避免使用裸指针管理动态内存。默认首选 unique_ptr,除非确实需要共享所有权才使用 shared_ptr,并在涉及环状结构时配合 weak_ptr 清理内存。合理搭配这三种工具,可写出既安全又高效的C++代码。
以上就是C++智能指针详解:unique_ptr、shared_ptr与weak_ptr用法及避坑指南的详细内容,更多关于C++智能指针的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













