C++智能指针 解读
一、什么是智能指针
智能指针是C++用于自动管理内存的一种手段,是RAII(资源获取即初始化)的典型例子,解决了通过new/delete手动管理内存导致的内存泄露、二次释放等问题。
二、智能指针核心原理
智能指针巧妙使用了类的思想,将指针封装成类,利用类的析构函数(自动调用)自动释放内存,无需手动干预。
三、智能指针存在必要
手动管理内存的痛点:
void bad_example() {
int* p = new int(10); // 动态分配内存
if (some_condition) {
return; // 提前返回,未执行 delete,内存泄漏
}
delete p; // 正常情况下释放
// 若后续误写 delete p; 会导致二次释放(崩溃)
return;
}
智能指针的出现解决了如上问题:无论函数正常返回还是异常退出,都会自动调用析构函数完成内存释放,确保了程序运行安全。
四、C++11三种智能指针
1)unique_ptr:
特点:同一时刻,只允许有一个unique_ptr指向同一对象(独占所有权),不支持拷贝,仅支持移动。
适用场景:管理单个对象/数组,无需共享所有权。
示例:
#include <memory>
#include <iostream>
int main() {
// 1. 管理单个对象(推荐用 make_unique 构造,更安全高效)
std::unique_ptr<int> up1 = std::make_unique<int>(10);
std::cout << *up1 << std::endl; // 解引用:10
std::cout << up1.get() << std::endl; // 获取原始指针(谨慎使用)
// 2. 所有权转移(move 后 up1 变为空指针)
std::unique_ptr<int> up2 = std::move(up1);
if (!up1) std::cout << "up1 已为空" << std::endl; // 输出:up1 已为空
// 3. 管理数组(需指定[],析构时自动调用 delete[])
std::unique_ptr<int[]> up3 = std::make_unique<int[]>(3);
up3[0] = 1; up3[1] = 2; up3[2] = 3;
// 4. 手动释放所有权(reset() 或赋值 nullptr)
up2.reset(); // 释放 up2 指向的内存,up2 变为空
up3 = nullptr; // 等价于 reset()
return 0; // 剩余智能指针自动析构,释放内存
}
注意点:禁止拷贝(std::unique_ptr<int> up4 = up2;编译报错)
2)shared_ptr:
特点:多个shared_ptr可指向同一个对象(共享所有权),增加引用计数来追踪对象被引用的次数。
- 新增一个share_ptr指向对象时,引用计数+1;
- 一个shared_ptr析构或重装时,引用计数-1;
- 当引用计数为0时,自动释放对象内存。
适用场景:多个指针共享同一对象(如多线程共享资源)。
示例:
#include <memory>
#include <iostream>
int main() {
// 1. 构造(推荐 make_shared,更高效)
std::shared_ptr<int> sp1 = std::make_shared<int>(30);
std::cout << "sp1 引用计数:" << sp1.use_count() << std::endl; // 1
// 2. 共享所有权(引用计数 +1)
std::shared_ptr<int> sp2 = sp1;
std::cout << "sp1 引用计数:" << sp1.use_count() << std::endl; // 2
std::cout << "sp2 引用计数:" << sp2.use_count() << std::endl; // 2
// 3. 重置一个指针(引用计数 -1)
sp1.reset();
std::cout << "sp1 重置后,sp2 引用计数:" << sp2.use_count() << std::endl; // 1
// 4. 自定义删除器(如管理文件指针、数组等)
auto delete_func = [](int* p) {
std::cout << "自定义删除器释放:" << *p << std::endl;
delete p;
};
std::shared_ptr<int> sp3(new int(30), delete_func);
return 0; // sp2、sp3 析构,sp2 引用计数变为 0 释放内存,sp3 调用自定义删除器
}
注意点:
避免循环引用(致命问题):两个shared_ptr互相指向对方,导致引用计数永远不为0,导致内存泄漏。
// 循环引用示例(错误)
struct Node {
std::shared_ptr<Node> next; // 互相引用
};
std::shared_ptr<Node> a = std::make_shared<Node>();
std::shared_ptr<Node> b = std::make_shared<Node>();
a->next = b;
b->next = a; // 循环引用,a 和 b 的引用计数永远为 2,析构时不释放
解决循环引用:weak_ptr(见下文)
3)weak_ptr:
特点:
- 不拥有对象的所有权,仅能“观察”share_ptr指向的对象;
- 不会增加引用计数,避免循环引用;
- 无法直接解引用(需要先通过lock()转换为share_ptr,判断对象是否存在)。
适用场景:解决shared_ptr循环引用
示例:
#include <memory>
#include <iostream>
struct Node {
std::weak_ptr<Node> next; // 用 weak_ptr 避免循环引用
};
int main() {
std::shared_ptr<Node> a = std::make_shared<Node>();
std::shared_ptr<Node> b = std::make_shared<Node>();
a->next = b; // weak_ptr 赋值,b 的引用计数仍为 1
b->next = a; // 无循环引用
std::cout << "a 引用计数:" << a.use_count() << std::endl; // 1
std::cout << "b 引用计数:" << b.use_count() << std::endl; // 1
// 访问 weak_ptr 指向的对象(需 lock() 转换)
if (std::shared_ptr<Node> temp = a->next.lock()) {
std::cout << "对象存活" << std::endl; // 输出:对象存活
}
a.reset(); // a 析构,引用计数变为 0,释放内存
if (std::shared_ptr<Node> temp = b->next.lock()) {
std::cout << "对象存活" << std::endl;
} else {
std::cout << "对象已释放" << std::endl; // 输出:对象已释放
}
return 0;
}
注意点:通过lock()尝试将weak_ptr转换为shared_ptr,对象存活则返回非空shared_ptr,否则返回空。
五、常见错误避雷
1)循环引用:shared_ptr互相引用导致内存泄漏,用weak_ptr解决。
2)裸指针构造智能指针:
int* p = new int(10);
std::shared_ptr<int> sp1(p);
std::shared_ptr<int> sp2(p); // 错误!两个 shared_ptr 管理同一裸指针,会二次释放
解决:始终使用make_shared方法或确保一个裸指针只被一个智能指针管理。
3)智能指针释放后使用原始指针
std::unique_ptr<int> up = std::make_unique<int>(10);
int* p = up.get();
up.reset(); // 释放内存
std::cout << *p; // 错误!p 成为悬垂指针
解决:慎用get()方法,避免长期持有原始指针。
六、总结
总结:智能指针是C++内存管理的“利器”,正确使用可使得程序更加安全、高效。一般优先使用unique_ptr,需要共享时用shared_ptr,循环引用时配合weak_ptr,可解决手动管理内存的痛点。
更多推荐



所有评论(0)