应优先使用std::unique_ptr和std::shared_ptr替代裸指针,前者实现独占所有权、零开销,后者通过引用计数支持共享所有权并需配合std::weak_ptr打破循环引用;禁止裸指针未初始化或越界解引用,推荐用make_unique/make_shared构造以保障异常安全。

避免裸指针,优先用 std::unique_ptr 和 std::shared_ptr
裸指针(int*、MyClass*)本身不带所有权语义,极易导致悬空、重复释放或内存泄漏。现代 C++ 中,绝大多数动态资源管理应交给智能指针。
-
std::unique_ptr表示独占所有权,轻量、无引用计数开销,适合“我创建、我销毁”的场景; -
std::shared_ptr适合共享所有权,但要注意循环引用——用std::weak_ptr打破; - 不要把
new出来的裸指针直接传给智能指针构造函数(如std::shared_ptr<int>(new int(42))</int>),这有异常安全风险;改用std::make_shared或std::make_unique; - 函数返回裸指针(尤其指向堆内存)是危险信号,优先返回
std::unique_ptr或引用。
所有裸指针初始化前必须显式赋值为 nullptr
未初始化的指针(尤其是栈上局部指针变量)内容是随机值,解引用会触发未定义行为,且难以复现。编译器不会警告,ASan 也未必捕获到。
- 声明即初始化:
int* p = nullptr;,而非int* p;; - 类成员指针在构造函数初始化列表中设为
nullptr(或用默认成员初始化器int* p = nullptr;); - 用
memset或std::fill初始化结构体数组时,确保指针字段也被清零; - 注意:
if (p)和if (p != nullptr)等价,但前者更简洁;后者在模板代码中有时更明确。
解引用前务必检查是否为 nullptr,但别过度防御
盲目加 if (p == nullptr) return; 不等于健壮——它掩盖设计缺陷。关键在于区分“可空”与“不可空”语义。
- 如果接口契约要求指针非空(例如函数参数代表必需资源),用引用(
MyClass&)代替指针,让编译器强制约束; - 若确实可能为空(如查找失败返回
nullptr),则调用方必须显式处理,不能假设“反正不会空”; - 避免在每个解引用前都写
if (p)——这说明你没理清所有权和生命周期; - 调试阶段可用断言:
assert(p != nullptr);,但生产代码中应靠设计保证,而非运行时拦截。
警惕指针生命周期超出所指对象的生命周期
这是悬空指针最常见来源:指针还活着,但对象早已析构。编译器几乎无法静态检测,UB 表现随机(崩溃、数据错乱、看似正常)。
- 不要返回局部对象的地址:
int* f() { int x = 42; return &x; }—— 绝对禁止; - 不要保存容器(如
std::vector)元素的原始指针/引用,因为插入/扩容可能使它们失效; - lambda 捕获指针时,确认被捕获对象的生命周期长于 lambda 本身;
- 用
std::string_view或std::span替代const char*/int*参数时,要同步管理底层数据生存期。
真正难的不是记住这十条,而是每次写 new 或 delete 时,停下来问一句:这里有没有更安全的所有权表达方式?指针的“活”和“所指对象的活”,是不是同一段上下文?
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











