
检查指针是否为 nullptr 再解引用
空指针异常本质是解引用了值为 nullptr 的指针,C++ 不做运行时检查,出错即崩溃或未定义行为。最直接的防御动作就是在每次 * 或 -> 前确认非空。
常见错误现象:函数返回 nullptr(比如 std::map::find 找不到时返回 end(),但有人误当有效迭代器用;或工厂函数失败返回 nullptr),后续直接调用 ptr->method() 导致段错误。
- 对裸指针,显式写
if (ptr != nullptr),别依赖隐式转换(if (ptr)虽可读但易被忽略语义) - 对智能指针,用
if (ptr)是安全的,但注意std::weak_ptr::lock()返回std::shared_ptr,仍需判空 - 在调试构建中可加断言:
assert(ptr != nullptr && "ptr must not be null");,发布版自动剔除,不影响性能
std::optional 和 std::expected 替代返回 nullptr
用 nullptr 表示“无值”是 C 风格遗留,容易被调用方忽略。现代 C++ 更推荐用类型系统把“可能为空”显式编码进接口。
使用场景:工厂函数、查找函数、解析函数等可能失败的操作。
立即学习“C++免费学习笔记(深入)”;
-
std::optional<t></t>比裸指针多一层语义:明确告诉调用方“这里可能没东西”,且强制解包前检查(opt.has_value()或if (opt)) - C++23 的
std::expected<t e></t>更进一步,能同时携带错误原因(比如std::errc::no_such_file),比只返回nullptr信息量大得多 - 注意:不要用
std::optional<:unique_ptr>></:unique_ptr>包裹智能指针——语义冗余,直接用std::unique_ptr<t></t>即可,它本身已支持空状态
构造函数和赋值中避免裸指针生命周期失控
很多空指针问题不是解引用瞬间发生的,而是对象析构后指针还挂着,变成悬垂指针;或者多个所有者对同一裸指针重复 delete,导致二次释放后某处再解引用就崩。
性能 / 兼容性影响:智能指针有极小开销(如 std::shared_ptr 的原子计数),但远小于一次段错误重启的代价。
- 成员变量优先用
std::unique_ptr<t></t>而非T*,确保析构时自动释放,且禁止拷贝(避免意外共享) - 若必须共享所有权,用
std::shared_ptr<t></t>,但别混用裸指针和智能指针管理同一块内存(比如用new T构造后交给shared_ptr,再另存一个裸指针) - 函数参数中,输入只读数据优先用
const T&或const std::shared_ptr<const t>&</const>,避免传T*引发所有权疑问
静态分析和编译器警告不能替代手动检查
Clang/GCC 的 -Wnull-dereference 或 MSVC 的 /analyze 能捕获部分明显问题,但覆盖有限——尤其跨函数、模板实例化、运行时分支后的情况。
容易踩的坑:以为开了 -Wall -Wextra 就高枕无忧;或过度依赖 [[nodiscard]] 却没处理返回值为空的分支。
- 启用
-Wdangling-gsl(GCC/Clang)或/wd5045(MSVC)可检测部分悬垂引用,但无法覆盖所有 RAII 外的生命周期漏洞 -
[[nodiscard]]对返回std::optional或std::expected的函数很有用,但对返回T*的函数加了也没用——编译器不强制你检查是否为空 - 真正可靠的防线仍是:函数契约写清楚(文档或 contract,如 C++20
[[expects: ptr != nullptr]]),调用方按契约检查
最麻烦的不是“不知道要检查”,而是“以为已经检查过了”。比如在多线程里,两次判空之间指针被其他线程置空;或者 std::shared_ptr 的 get() 结果被存为裸指针,之后原 shared_ptr 析构了。这类问题不会报编译警告,也不会触发断言,只在特定时序下崩溃。











