处理作为函数的输出参数传入的空指针的首选方法是什么?我可以ASSERT,但我觉得让图书馆崩溃程序并不好。相反,我正在考虑使用例外。
答案 0 :(得分:11)
抛出异常!这就是他们的目的。然后,您的图书馆用户可以决定他们是想要优雅地处理它还是崩溃和烧毁。
另一个特定的解决方案是返回有效类型的无效值,例如返回索引的方法的负整数,但您只能在特定情况下使用它。
答案 1 :(得分:3)
如果不允许空指针,我会使用断言。如果为空指针抛出异常,则有效地允许它们作为参数,因为您指定了这些参数的行为。如果你不允许空指针,但你仍然得到它们,那么一些代码肯定有一个bug。因此,在我看来,在某些更高级别“处理”它是没有意义的。
要么允许调用者传递空指针并通过抛出异常来处理这种情况,让调用者正确反应(或让异常传播,如调用者所希望的那样),或者你不允许空指针和{ {1}}它们,可能在发布模式下崩溃(未定义的行为)或使用在发布模式下仍处于活动状态的指定断言宏。后一种哲学是由诸如assert
之类的函数所采用的,而前一种哲学则是由诸如strlen
之类的函数所采用的。后一个函数显式地规定了越界值的行为,而前者只是为传递的空指针声明了未定义的行为。
最后,你怎么会“处理”空指针?
vector<>::at
在我看来,这很丑陋。如果在函数中断言指针为null,则此类代码变为
try {
process(data);
} catch(NullPointerException &e) {
process(getNonNullData());
}
我认为这远远优于它,因为它不使用控制流的异常(提供非NULL源作为参数)。如果您不处理异常,那么您也可能已经在if(!data) {
process(getNonNullData());
} else {
process(data);
}
中使用断言失败,这将直接指向崩溃发生的文件和行号(并且一个调试器,你实际上可以得到一个堆栈跟踪)。
在我的应用程序中,我总是采用process
路线。我的理念是空指针参数应该完全由非异常路径处理,或者声明为非NULL。
答案 2 :(得分:2)
两者都做。
在开发过程中可以捕获的任何内容都将中止该过程,这将使开发人员明白需要修复它。
如果确实让它经过了测试,那么仍有一个例外,一个强大的程序可以处理。
这很容易放入宏(必须是宏而不是内联,以便断言正确报告行号 - 感谢@RogerPate指出这一点):
#define require_not_null(ptr) \
do { assert(ptr); if (!(ptr)) throw std::logic_error("null ptr"); } while (0)
答案 3 :(得分:1)
如果您重视性能,断言将在发布时关闭。他们在那里捕捉不应该发生的问题,不应该被用来捕捉现实生活中可能发生的事情。这就是例外情况。
但是让我们备份一秒钟。在哪里可以保证如果取消引用空指针会发生什么,无论是否写入?它可能会崩溃,但它不会在每个操作系统,每个编译器或任何其他任何东西中崩溃。它为你崩溃只是你的好运。
如果你不打算自己创建对象并将指针传递给你,我会说抛出一个异常,就像我经常看到“out”params一样。
答案 4 :(得分:1)
如果您正在为终极飞机编程自动驾驶系统,我应该建议您优雅地处理异常。
请阅读“合同编程”的埃菲尔规范(确实是一种非常好的语言),你会受到启发。如果你能处理这件事,一定不会崩溃。
答案 5 :(得分:1)
如果你抛出,客户可以决定重新抛出,或者不处理异常,或者崩溃或者调用退出或尝试恢复或....
如果您崩溃,客户端会崩溃。
所以扔掉,给你的客户更多的灵活性。
答案 6 :(得分:1)
我既不会引发异常也不会使用assert,这就是C ++标准库所做的。考虑一下库中最简单的函数strlen()
。如果它引发了异常,您将如何处理它?并且断言不会在生产代码中触发。唯一明智的做法是明确说明不能使用NULL指针作为参数调用该函数,这样做会导致未定义的行为。
答案 7 :(得分:0)
使用例外的好处是您让客户端代码决定如何处理异常情况。这适用于参数为非null的情况是函数的规定前提条件。但是,对于采用可选输出参数的函数,传递NULL可以表示客户端对该值不感兴趣。据推测,您正在使用返回值来表示成功或失败,如果是这种情况,您可以简单地检测NULL并在参数必需时返回错误代码,或者如果参数是可选的,则简单地忽略它。这样可以避免异常开销,并且仍然可以在客户端进行错误处理。