对于某些语言(即 C++)资源泄漏不应成为原因
C++ 基于 RAII。
如果您有可能失败、返回或抛出的代码(即最普通的代码),那么您应该将指针包裹在智能指针中(假设您有一个非常好的理由没有在堆栈上创建您的对象)。
返回码更详细
它们很冗长,并且倾向于发展成以下内容:
if(doSomething())
{
if(doSomethingElse())
{
if(doSomethingElseAgain())
{
// etc.
}
else
{
// react to failure of doSomethingElseAgain
}
}
else
{
// react to failure of doSomethingElse
}
}
else
{
// react to failure of doSomething
}
最后,你的代码是识别指令的集合(我在生产代码中看到过这种代码)。
这段代码可以翻译成:
try
{
doSomething() ;
doSomethingElse() ;
doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
// react to failure of doSomething
}
catch(const SomethingElseException & e)
{
// react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
// react to failure of doSomethingElseAgain
}
将代码和错误处理完全分开,这可能是一件好事。
返回码更脆弱
如果不是来自某个编译器的一些晦涩的警告(请参阅“phjr”的评论),它们很容易被忽略。
对于上面的例子,假设有人忘记处理可能的错误(这种情况发生......)。 “返回”时会忽略该错误,并且以后可能会爆炸(即 NULL 指针)。同样的问题不会出现异常。
错误不会被忽略。有时候,你希望它不爆炸,但是……所以你必须谨慎选择。
有时必须翻译返回码
假设我们有以下函数:
- doSomething,它可以返回一个名为 NOT_FOUND_ERROR 的 int
- doSomethingElse,可以返回 bool "false"(表示失败)
- doSomethingElseAgain,它可以返回一个错误对象(包含 __LINE__、__FILE__ 和一半的堆栈变量。
- doTryToDoSomethingWithAllThisMess 其中,嗯...使用上述函数,并返回类型为...的错误代码...
doTryToDoSomethingWithAllThisMess 如果其中一个调用函数失败,返回的类型是什么?
返回码不是通用解决方案
操作员不能返回错误代码。 C++ 构造函数也不能。
返回码意味着你不能链接表达式
上述观点的推论。如果我想写怎么办:
CMyType o = add(a, multiply(b, c)) ;
我不能,因为返回值已被使用(有时,它无法更改)。所以返回值就变成了第一个参数,作为引用发送……还是不行。
输入异常
您可以为每种异常发送不同的类。资源异常(即内存不足)应该很轻,但其他任何事情都可以根据需要很重(我喜欢 Java Exception 给我整个堆栈)。
然后可以对每个捕获进行专门化。
永远不要在不重新抛出的情况下使用 catch(...)
通常,您不应隐藏错误。如果您不重新抛出,至少将错误记录在文件中,打开消息框,无论如何......
例外是...... NUKE
异常的问题是过度使用它们会产生充满尝试/捕获的代码。但问题出在其他地方:谁使用 STL 容器尝试/捕获他/她的代码?尽管如此,这些容器仍然可以发送异常。
当然,在 C++ 中,永远不要让异常退出析构函数。
异常是......同步
确保在它们让您的线程陷入困境或在您的 Windows 消息循环中传播之前抓住它们。
解决方案可能是混合它们?
所以我想解决方案是在应该不发生的事情时抛出。并且当某些事情可能发生时,然后使用返回码或参数来使用户能够对其做出反应。
那么,唯一的问题是“什么是不应该发生的事情?”
这取决于您的功能合同。如果函数接受一个指针,但指定指针必须为非NULL,那么当用户发送一个NULL指针时抛出异常是可以的(问题是,在C++中,函数作者什么时候不使用引用代替指针,但是...)
另一种解决方案是显示错误
有时,您的问题是您不希望出现错误。使用异常或错误返回码很酷,但是……您想了解它。
在我的工作中,我们使用一种“断言”。无论调试/发布编译选项如何,它都会根据配置文件的值:
- 记录错误
- 打开一个带有“嘿,你有问题”的消息框
- 打开一个带有“嘿,你有问题,要调试”的消息框
在开发和测试中,这使用户能够在检测到问题时准确地查明问题,而不是之后(当某些代码关心返回值时,或在 catch 内)。
很容易添加到遗留代码中。例如:
void doSomething(CMyObject * p, int iRandomData)
{
// etc.
}
引出一种类似的代码:
void doSomething(CMyObject * p, int iRandomData)
{
if(iRandomData < 32)
{
MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
return ;
}
if(p == NULL)
{
MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
throw std::some_exception() ;
}
if(! p.is Ok())
{
MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
}
// etc.
}
(我有类似的宏,只在调试时有效)。
注意,在生产中,配置文件不存在,所以客户端永远看不到这个宏的结果......但是在需要时很容易激活它。
结论
当您使用返回码进行编码时,您正在为失败做好准备,并希望您的测试堡垒足够安全。
当您使用异常编码时,您知道您的代码可能会失败,并且通常会将 counterfire catch 放在代码中选定的战略位置。但通常,您的代码更多的是关于“它必须做什么”而不是“我担心会发生什么”。
但是,当您编写代码时,您必须使用您可以使用的最佳工具,有时是“永远不要隐藏错误,并尽快显示它”。我上面所说的宏遵循这个哲学。