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

Posted

技术标签:

【中文标题】C ++中的安全指针取消引用【英文标题】:Safe pointer dereferencing in C++ 【发布时间】:2015-05-21 05:57:42 【问题描述】:

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

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;

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

问题是:我是在重新发明***吗?标准或增强中有一些相关的解决方案吗?或者你有一些关于解决方案的 cmets?

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

【问题讨论】:

是否有任何代码实际处理nullptr 案例?例如。像if ( !obj ) std::cout &lt;&lt; "Null, but still okay, moving on with something meaningful\n"; else std::cout &lt;&lt; "Non-Null, doing something else\n"; 这样的东西。还是nullptr 案例总是“退出,因为这是错误的”路径? 此代码存在,但很少见。在大多数情况下,我们无法继续。我们无能为力。整个例程毫无意义。那么也许这个对象的存在是一个前置条件?我需要检查它,然后手动取消引用指针而不用担心? 这正是你需要为自己澄清的,恕我直言。如果这是一个先决条件,那么assertion 可能更合适。否则,这显然是一件特殊的事情(0.01% 是相当特殊的),所以exception 是合适的。 Afaik,图书馆里没有这样的东西。 我认为您应该详细说明此主题并将其发布为答案。 如果从 getObjectThatMayVeryRarelyBeNull 函数中抛出异常怎么办? 【参考方案1】:

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

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

assert的优势:

在发布模式下运行时免费 让开发人员清楚地知道,他有责任让这种情况永远不会发生。

assert的缺点:

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

您还应该考虑编写一个方法getObjectThatMayNeverBeNullButDoingTheSameAsGetObjectThatMayVeryRarelyBeNull():该方法保证返回一个指向对象的非空指针,该对象在其他方面与您的原始方法完全等效。但是,如果您可以加倍努力,我强烈建议您不要返回原始指针。拥抱 C++11 并按值返回对象 :-)

【讨论】:

我想要一些断言,它们不会在发布时被删除。我想区分断言和先决条件。关于该主题的任何好的链接? 断言是先决条件(或者至少是最接近它的东西)。但是没有什么能阻止你同时使用断言和异常。 :) 是的,解决方案非常明显,我相信有人在这方面做得很好,并且有一个一致且通用的框架。不过,我不知道。例如,Boost.assert 没有前置条件。【参考方案2】:

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

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

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

您可能可以识别系统中可能合理预期返回null 的类。对于这些类,null 可能是有效的业务逻辑,而不是指示实现错误的边缘案例。对于可能返回 null 的类,额外的检查可能值得安全。从这个意义上说,检查null 感觉更像是业务逻辑,而不是低级别的实现细节。不过,全面地将所有的指针遵从性包装在deref_or_throw 中可能是一种自成其毒的治疗方法。

【讨论】:

函数getObjectThatMayVeryRarelyBeNull()应该有可能返回null。这是罕见的,但它是可能的。但是由于它很少见,大多数例程(除了一些特殊的例程)都希望它返回一个对象。否则它们就毫无意义。 我很想将 null 作为有效的返回值删除,并且可能考虑使用另一个选项来指示这种非常罕见的情况。在不知道您的用例的情况下,我无法准确推测那会是什么。似乎您的班级可以返回一个“令人惊讶”的结果,并非所有家属都能正确处理,因此在那里返回 null 似乎确实很危险。当然,您的代码的未来读者不必了解 getObjectThatMayVeryRarelyBeNull() 0.01% 的时间返回 null 的微妙之处 这确实是一个棘手的设计问题,这就是为什么我不问它的原因:) 我限制了可能的解决方案的范围,因此getObjectThatMayVeryRarelyBeNull() 的界面无法更改。 @Mikhail getObjectThatMayVeryRarelyBeNull() 的接口无法更改 好的,但是...可以在函数内部检查它是否要返回 null,在这种情况下抛出函数内部的异常? @PaperBirdMaster 这是可能的,但这是一个突破性的变化。因为实际上它一个接口更改(尽管二进制兼容),因为我们删除了隐式noexcept

以上是关于C ++中的安全指针取消引用的主要内容,如果未能解决你的问题,请参考以下文章

通过取消引用更改 NSArray 中的值?

C中的棘手指针算法:** k

C编程中void指针的概念

c语言关于指针引用的问题

XMM 寄存器中的取消引用指针(收集)

C语言指针的内存分配和Java中的引用