Melon Playground甜瓜游乐场
98.73M · 2026-03-22
大家好,我是AI_搬运工。
这是我在稀土掘金的第一篇推文,也是「现代C++进阶指南」系列的开篇。
为什么第一篇要写智能指针?
因为在我过去几年的C++开发经历中,内存泄漏和悬垂指针这两个幽灵,困扰了我无数次。每次项目上线前通宵排查内存问题,每次在析构函数里小心翼翼地配对new[]和delete[],每次看到Core Dump时的绝望——这些经历,我相信每一个C++开发者都不陌生。
C++11带来的智能指针,不是语法糖,而是一场资源管理革命。
今天,我们不谈虚的,直击核心:std::unique_ptr、std::shared_ptr、std::weak_ptr,怎么用?什么时候用?底层原理是什么?
先看一段“经典”代码:
cpp
void riskyFunction() {
int* p = new int(42);
if (someCondition()) {
return; // 内存泄漏!
}
delete p;
}
这个例子太过简单,真实项目中往往是这样:
delete更可怕的是悬垂指针:
cpp
int* getBadPointer() {
int local = 10;
return &local; // 返回局部变量地址,悬垂!
}
或者:
cpp
int* p = new int(5);
int* q = p;
delete p;
// 此时q成为悬垂指针,使用即UB
RAII(Resource Acquisition Is Initialization) 是C++解决资源管理的核心思想:让对象的生命周期管理资源,利用栈对象自动析构的特性,确保资源被释放。
智能指针,就是RAII思想在动态内存管理上的完美体现。
std::unique_ptr代表独占所有权——同一时刻,只有一个unique_ptr指向一块内存。
cpp
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::make_unique 是 C++14 引入,C++11 需要自己 new
// 推荐始终使用 make_unique
std::unique_ptr<int> p2 = p1; // 编译错误!不能拷贝
std::unique_ptr<int> p3 = std::move(p1); // 所有权转移,p1变为空
std::unique_ptr的原始指针版本,在开启优化后,生成的汇编代码与裸指针完全一致。它没有虚函数表,没有引用计数,所有操作都是编译期确定的。
cpp
// 裸指针版本
void raw(int* p) {
delete p;
}
// unique_ptr版本
void smart(std::unique_ptr<int> p) {
// 析构自动释放
}
// 汇编几乎相同,unique_ptr不引入任何额外开销
cpp
// 工厂函数返回unique_ptr,明确所有权转移
std::unique_ptr<Widget> createWidget() {
return std::make_unique<Widget>();
}
// 容器存储unique_ptr
std::vector<std::unique_ptr<Widget>> widgets;
widgets.push_back(std::make_unique<Widget>());
什么时候用unique_ptr?
std::shared_ptr通过引用计数实现共享所有权。每个shared_ptr内部维护一个控制块,包含:
cpp
auto p1 = std::make_shared<int>(42);
auto p2 = p1; // 引用计数变为2
std::cout << p1.use_count(); // 输出2
使用shared_ptr需要付出的代价:
cpp
struct Node {
std::shared_ptr<Node> next;
~Node() { std::cout << "dtorn"; }
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->next = a; // 循环引用!内存泄漏!
解决方式:使用std::weak_ptr打破循环
cpp
struct Node {
std::weak_ptr<Node> next; // 弱引用,不增加引用计数
~Node() { std::cout << "dtorn"; }
};
weak_ptr不控制对象生命周期,需要使用时通过lock()获取shared_ptr:
cpp
if (auto sp = weak.lock()) {
// 安全使用sp
} else {
// 对象已被销毁
}
原则:能用unique_ptr就不用shared_ptr
看一个实际例子:一个简单的链表
cpp
// 原始指针版本:充满风险
struct NodeRaw {
int data;
NodeRaw* next;
~NodeRaw() { delete next; } // 递归删除,风险大
};
// 现代C++版本:安全优雅
struct Node {
int data;
std::unique_ptr<Node> next; // 独占所有权,自动析构
};
class List {
std::unique_ptr<Node> head;
public:
void push_front(int val) {
auto newNode = std::make_unique<Node>();
newNode->data = val;
newNode->next = std::move(head);
head = std::move(newNode);
}
// 析构自动处理,无需手动写delete
};
std::unique_ptr,它是最轻量、语义最清晰的选择std::make_unique和std::make_shared,异常安全且更高效get() ,除非与遗留C API交互weak_ptr打破循环引用,这是shared_ptr使用者的必修课new和delete,让智能指针管理动态内存cpp
// 管理FILE资源
auto fileDeleter = [](FILE* f) { if(f) fclose(f); };
std::unique_ptr<FILE, decltype(fileDeleter)> filePtr(fopen("test.txt", "r"), fileDeleter);
智能指针是C++11给这门语言带来的最实用的礼物之一。它不是让C++变“慢”了,而是让C++变“安全”了。
掌握智能指针,是迈出现代C++开发的第一步。
下一篇,我们将深入移动语义与完美转发,聊聊C++11另一项改变性能格局的特性。如果感兴趣,欢迎关注,不错过后续更新。
欢迎在评论区留下你的问题或经验——你遇到过最诡异的内存问题是什么?你是如何解决的?
本文章由AI生成,如有侵权请联系删除
如果觉得文章有帮助,点赞、收藏、关注支持一下,你的支持是我持续输出的动力。
我是AI_搬运工,我们下篇见。