C++ 中的安全指针取消引用

Safe pointer dereferencing in C++

在我们的代码库中,我们有很多这样的结构:

auto* pObj = getObjectThatMayVeryRarelyBeNull();
if (!pObj) throw std::runtime_error("Ooops!");
// Use pObj->(...)

在 99.99% 的情况下,不会触发此检查。我正在考虑以下解决方案:

auto& obj = deref_or_throw(getObjectThatMayVeryRarelyBeNull());
// Use obj.(...)

其中deref_or_throw声明如下:

template<class T> T& deref_or_throw(T* p) {
  if (p == nullptr) { throw std::invalid_argument("Argument is null!"); }
  return *p;
}

该代码更清晰并且可以按我的需要工作。

问题是:我是在重新发明轮子吗?标准或提升中是否有一些相关的解决方案?或者您对解决方案有什么意见?

PS。相关问题(没有令人满意的答案):Is there a C++ equivalent of a NullPointerException

您可以并且可能应该在设计时考虑 null 个案例。例如,在您的问题域中,class 方法何时对 return null 有意义?什么时候使用 null 个参数调用函数才有意义?

从广义上讲,尽可能从代码中删除 null 引用是有益的。换句话说,您可以编程为 "this will not be null here... or there" 的不变量。这有很多好处。您不必通过在 deref_or_throw 中包装方法来混淆代码。您可能会从代码中获得更多语义,因为,事物 null 在现实世界中的出现频率是多少?如果您使用异常而不是 null 值来指示错误,您的代码可能更具可读性。最后,您减少了错误检查的需要,也降低了 运行 时间错误(可怕的空指针取消引用)的风险。

如果您的系统在设计时没有考虑 null 个案例,我会说最好不要管它,不要 疯狂地用 [= 包装所有东西14=]。也许采取敏捷方法。在编码时,检查 class 对象作为服务提供的合同。这些合同多久可以合理预期 return null?在这些情况下 null 的语义值是什么?

您可能会在您的系统中识别出合理预期 return null 的 classes。对于这些 classes,null 可能是有效的业务逻辑,而不是指示实施错误的边缘案例。对于可能预期 return null 的 classes,额外的检查可能是值得的。从这个意义上说,检查 null 感觉更像是业务逻辑,而不是低级实现细节。不过,总的来说,系统地将所有指针遵循包装在 deref_or_throw 中可能是一种治愈方法,它本身就是一种毒药。

有两种方法可以处理 "rare case of null-pointer" 问题。首先,它是提议的异常解决方案。为此,deref_or_throw 方法是一件好事,尽管我更愿意抛出 runtime_error,而不是 invalid_argument 异常。如果您愿意,也可以考虑将其命名为 NullPointerException

但是,如果 ptr != nullptr 情况实际上是算法的先决条件,您应该尽力在这种非空情况下实现 100% 安全。在这种情况下,assert 是合适的。

assert的优点:

  • 在发布模式下运行时无成本
  • 让开发人员crystal清楚,他有责任避免这种情况发生。

assert的缺点:

  • 发布模式下的潜在未定义行为

您还应该考虑编写一个方法 getObjectThatMayNeverBeNullButDoingTheSameAsGetObjectThatMayVeryRarelyBeNull():保证 return 一个指向对象的非空指针的方法,该对象在其他方面完全等同于您的原始方法。但是,如果你能多走一步,我强烈建议不要 return 使用原始指针。按值拥抱 C++11 和 return 对象 :-)