请纠正我的异常处理问题

Please disabuse me of exceptions handling problem

本文关键字:异常 处理问题 我的      更新时间:2023-10-16

许多程序员喜欢使用异常来应对程序中的异常情况(如运行时错误)。为了更清楚地说明我的问题,我发布了如下两个代码片段。我的问题是"试试……"在错误检查和处理方面,Catch块比程序员自己做更安全、更强大、更聪明吗?"我想听听你善意的建议。提前感谢!

  1. 使用试一试……catch块

    std::map<std::string, Array2D<unsigned short>* > DataPool;
    try
    {  
       for (int i = 0; i < 4; i++)
       {   
           std::stringstream ss;
           ss << "I" << i;
           std::string sKey = ss.str();
           // Insert an element into the map container
           DataPool.insert(std::make_pair(sKey, new Array2D<unsigned short>(2288, 2288))); 
       }
    }
    catch (bad_alloc&)
    {  
        cout << "Error allocating memory." << endl;
        // Free allocated memory
        for (std::map<std::string, Array2D<unsigned short>* >::iterator it = DataPool.begin(); it != DataPool.end(); it++)
           delete ( *it ).second;
       DataPool.clear();
    }
    
  2. 自己处理错误

    std::map<std::string, Array2D<unsigned short>* > DataPool;
    Array2D<unsigned short>* pBuffer = NULL;    
    for (int i = 0; i < 4; i++)
    {   
        std::stringstream ss;
        ss << "I" << i;
        std::string sKey = ss.str();
        pBuffer = new Array2D<unsigned short>(2288, 2288);
        if (pBuffer == NULL)
        {  
           // Free allocated memory
           for (std::map<std::string, Array2D<unsigned short>* >::iterator it = DataPool.begin(); it != DataPool.end(); it++)
               delete ( *it ).second;
           DataPool.clear();
           break;
        }
        // Insert an element into the map container
        DataPool.insert(std::make_pair(sKey, pBuffer)); 
    }
    

至少对于上面的代码片段,答案很简单:第一个代码片段"更安全"(尽管存在其他代码问题),因为第二个代码片段并没有做您认为它做的事情。

第二个错误检查将不起作用,因为new在符合标准的c++编译器中永远不会返回null。c++标准要求new 在分配失败时抛出bad_alloc。因此,处理new抛出的bad_alloc异常是唯一检查new失败的方法

请注意,我谈论的是关于c++标准和你的问题中的代码片段。c++编译器实现可能有偏离这种行为的选项,这些选项(应该)被完整地记录下来。正如Ben Voigt所提到的,std::nothrow可以用来使new返回NULL

不推荐使用。

RAII学习。理解它如何比try/catch/finally更好地提供异常安全性。那就用它。

通过您的所有更好(更安全,更强大/更灵活,更智能)的措施,RAII优于try/catch

您的第一个问题是您没有使用RAII。RAII不仅是c++中正确处理资源的基础,而且几乎是编写异常安全代码所必需的。

你的小例子并没有很好地展示异常处理的优点,恰恰是因为它太小了,不需要异常提供的非本地控制流。在较大的程序中,通常导致问题的代码和知道如何对问题作出反应的代码彼此相距很远,异常为报告执行过程中的问题提供了一种干净的通用方法。在某些情况下,异常对于与发生的错误无关的其他控制流也很有用,但需要从一段代码中选择另一个出口。

使用RAII,您的示例看起来像这样:

std::map<std::string, std::unique_ptr<Array2D<unsigned short> > > DataPool;
for (int i(0); i < 4; ++i) {
    std::stringstream ss;
    ss << "I" << i;
    std::string key(ss.str());
    std::unique_ptr<Array2D<unsigned short> > value(
        new Array2D<unsigned short>(2288, 2288));
    DataPool.insert(std::make_pair(key, value));
}

请注意,没有任何手动清理,因为它都是由RAII的魔力管理的。所有需要清理的资源都放在对象中,这些对象在不能抛出的操作中自动管理它们在作用域退出时的清理。这意味着,当退出作用域时,这些资源保证已被清理。为了简洁起见,我在这个例子中使用std::unique_ptr,但是如果你的系统还不支持它,那么你也可以使用一个类来包装std::map,并确保所有插入的值随后被删除。

"Error allocating memory."消息在这部分代码中不太可能有用,但是调用代码可能知道正确的操作是什么。这是因为调用代码有更多关于代码试图解决的实际问题的上下文,而不是这个小片段,它应该被编写成在各种不同的情况下可用。在各种不同的情况下都是有用的,这意味着它应该尝试做需要它做的事情,并简单地向调用代码报告它在执行过程中遇到的任何问题(而不是打印到一些流中,这可能是完全不合适的)。

返回码也可以允许代码向其调用者报告问题,但是使用返回码排除了从函数返回值,并且与c++中的构造模型不兼容(c++中的构造函数必须创建一个工作对象或抛出异常)。返回代码也可能导致非常不可读的代码,其中大部分代码与错误处理有关,而不是代码试图解决的实际问题。返回代码的另一个问题是,如果异常抛出路径很少发生,它们可能比异常效率低(警告收集器—不要相信我的话!如果您关心性能,那么您应该度量代码的实际性能。

try…Catch会使你的代码运行得更慢。最佳实践是首先手动处理错误,这样您就可以控制可能发生的每个错误方面(并决定如何处理错误)

在c#中,通常所有的顶级函数都包装在try…