选择指针类型不是站队,也并非非黑即白的抉择。
原则其实很清晰:想表达所有权,就用智能指针;只是临时借用,就用普通指针或引用。

在项目中,我见过许多由内存引发的事故,主要原因往往是资源在函数、模块、线程之间流转时,所有权不明——没人敢删,也不敢不删,最终导致内存泄漏。现代 C++ 的核心思想之一,正是将生命周期和所有权写进类型系统,让编译器替你守护契约,而不是依赖注释、文档或程序员的记忆。因此,智能指针并非简单的语法糖或性能负担,而是一种显式表达资源责任的工程工具。
一、使用前,先问一句:谁拥有这块资源?
这是所有内存管理相关问题的起点。如果回答不上来,代码迟早会出问题。
- 唯一所有者
std::unique_ptr:零运行时开销、移动语义清晰、保证异常安全。
- 多个所有者共享生命周期
std::shared_ptr:需谨慎使用,因为“共享”往往意味着复杂性的扩散。
- 只观察、不延长寿命
std::weak_ptr:专门用于解决循环引用和悬空指针的风险。
- *不拥有、只是临时用一下
T&(非空)或 `T`(可空)**:明确表示“我不负责释放”。
来看一个反面案例:
class OrderProcessor {
Order* current; // 谁 delete?不知道。
public:
void set(Order* o) { current = o; }
~OrderProcessor() { /* 删还是不删?删了可能 double-free,不删就泄漏 */ }
};
如果换成 unique_ptr,所有权意图瞬间清晰:
class OrderProcessor {
std::unique_ptr<Order> current; // 我拥有它,我负责释放
public:
void set(std::unique_ptr<Order> o) { current = std::move(o); }
// 析构函数自动释放,无需手写 delete,异常路径也安全
};
通过类型本身,就能一眼看出资源的责任归属,这是现代 C++ 在内存安全方面带来的巨大进步,其背后离不开对计算机基础中编译器与类型系统的深度利用。
二、能不用指针,就别用指针
动态分配(new/delete)在现代 C++ 中并非常态,值语义永远是第一选择。
- 字符串用
std::string,别碰 char*。
- 数组用
std::vector,别手写 new T[]。
- 配置对象直接传
Config 值,别传 Config*。
当然,如果需要运行时多态(如基类指针指向派生类对象),此时应优先使用 std::unique_ptr<Base>,而非裸指针。因为如果使用值语义,会导致对象切片,派生类的特有部分会直接丢失。
在我们的团队实践中,要求如果一个成员可以用值语义表示,就不允许使用指针。这种对基础 & 综合编码原则的坚持,显著提升了代码的健壮性和可维护性。
三、普通指针什么时候可以用?
智能指针不是万能的,普通指针在以下场景不仅是合理的,甚至是必要的。
1、表达「借用」语义
当函数参数只是读取对象,并不要求获取所有权时:
void log_order(const Order& order); // 推荐:非空、只读
void parse_data(const char* buffer); // 可空、C 风格兼容
在这种情况下,使用 shared_ptr 作为参数反而是错误的,因为它暗示了不必要的所有权共享。
2、与 C 或操作系统 API 交互
fopen 返回 FILE*,CreateFile 返回 HANDLE,CUDA 函数返回 void*——这些外部接口你无法改变。此时,可以采用“边界接裸指针,内部转 RAII”的策略来封装,既兼容了 API,又保证了资源安全。
auto file = std::unique_ptr<FILE, decltype(&fclose)>{
fopen("data.bin", "rb"), &fclose
};
3、指向非堆对象
智能指针设计用于管理需要显式释放的堆资源。如果你指向的是栈变量、全局对象或 std::vector 中的元素,使用智能指针将是一场灾难:
std::vector<int> v = {1, 2, 3};
int* p = &v[0]; // 正确:只是观察栈上容器的元素
// auto bad = std::make_unique(&v[0]); // 错误!v[0] 不是 new 出来的
四、澄清几个致命误区
误区一:智能指针能防止所有悬空指针。
错。weak_ptr 只能检测它所观察的、由 shared_ptr 管理的对象是否已被销毁。但如果你写了 T* p = shared_obj.get(),后续使用这个裸指针 p 仍然是危险的,因为智能指针管理的是所有权的生命周期,而非任意指针的有效性。
误区二:unique_ptr 影响性能。
在标准优化(-O2)下,unique_ptr 的析构操作通常会被内联,生成的汇编代码与手动 delete 几乎一致,几乎没有运行时开销。
误区三:C 风格指针更灵活。
“灵活”的潜台词往往是“失控”。当你允许裸指针持有所有权时,就失去了编译器的静态检查保护,将内存安全的负担完全交给了容易出错的人工检查。
总结
智能指针不是解决所有内存问题的“银弹”,但它成功地将 “谁负责释放资源” 这个问题,从模糊的运行时约定提前到了明确的编译期检查。
作为一名合格的程序员,关键不在于完全不用普通指针,而在于清楚地知道什么时候该用、怎么用、以及为什么不用。理解并应用这些关于C/C++中指针与内存管理的工程智慧,能让你写出更安全、更清晰的代码。
希望以上基于实践经验的总结对你有帮助。如果你想与更多开发者交流此类后端 & 架构话题,欢迎来云栈社区探讨。