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

Safe pointer dereferencing in C++

本文关键字:取消 引用 指针 安全 C++      更新时间:2023-10-16

在我们的代码库中,我们有许多类似的构造:

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。相关问题(没有令人满意的答案):是否有相当于NullPointerException 的C++

有两种方法可以处理"罕见的空指针情况"问题。首先,它是提出的异常解决方案。为此,方法deref_or_throw是一件好事,尽管我更喜欢抛出runtime_error,而不是invalid_argument异常。如果您愿意,也可以考虑将其命名为NullPointerException

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

assert:的优点

  • 在发布模式下运行时无成本
  • 让开发人员清楚地知道,他要为永远不会发生这种情况负责

assert:的缺点

  • 释放模式下潜在的未定义行为

您还应该考虑编写一个方法getObjectThatMayNeverBeNullButDoingTheSameAsGetObjectThatMayVeryRarelyBeNull():该方法保证返回一个指向与原始方法完全等效的对象的非null指针。然而,如果你能多做一点,我强烈建议你无论如何都不要返回原始指针。接受C++11并按值返回对象:-)

您可以而且可能应该考虑null案例进行设计。例如,在您的问题域中,类方法何时返回null有意义?什么时候用null参数调用函数才有意义?

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

如果您的系统在设计时没有考虑到null的情况,我认为最好不要管它,不要疯狂地用deref_or_throw包装所有东西。也许可以采取敏捷的方法。在编写代码时,检查类对象作为服务提供的契约。这些合同多长时间才能合理地返回null?在这些情况下,null的语义价值是什么?

您可能会确定系统中可能会合理地返回null的类。对于这些类,null可能是有效的业务逻辑,而不是指示实现错误的边缘情况。对于可能期望返回null的类,额外的检查可能值得安全。从这个意义上说,检查null感觉更像是业务逻辑,而不是低级别的实现细节。不过,总体而言,在deref_or_throw中系统地包装所有指针的尊重可能是一种治疗方法,但这本身就是一种毒药。