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 << "Null, but still okay, moving on with something meaningful\n"; else std::cout << "Non-Null, doing something else\n";
这样的东西。还是nullptr
案例总是“退出,因为这是错误的”路径?
此代码存在,但很少见。在大多数情况下,我们无法继续。我们无能为力。整个例程毫无意义。那么也许这个对象的存在是一个前置条件?我需要检查它,然后手动取消引用指针而不用担心?
这正是你需要为自己澄清的,恕我直言。如果这是一个先决条件,那么assert
ion 可能更合适。否则,这显然是一件特殊的事情(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 ++中的安全指针取消引用的主要内容,如果未能解决你的问题,请参考以下文章