STD异常会导致不安全的使用

std exceptions inviting unsafe usage?

本文关键字:不安全 异常 STD      更新时间:2023-10-16

建议您总是抛出从std::exception派生的东西,并且有一些预定义的专门化,例如std::runtime_error

std::exception的接口以非抛出访问器的形式给出。太好了。现在看看std::runtime_error

的构造函数
class runtime_error : public exception {
public:
  explicit runtime_error (const string &);
};

如果我输入这个

try {
    foo ();
}
catch (...) {
    throw std :: runtime_error ("bang");
}

完全有可能foo抛出,因为它内存不足,在这种情况下,构造runtime_errorstring参数也可以抛出。这将是一个throw表达式,它本身也会抛出:这会不会调用std::terminate ?

这是否意味着我们应该这样做:

namespace {
    const std :: string BANG ("bang");
}
...
try {
    foo ();
}
catch (...) {
    throw std :: runtime_error (BANG);
}

但是等等,这个也不行,不是吗?因为runtime_error要复制它的参数,这也可能抛出…

…因此,这是否意味着没有安全的方法来使用std::exception的标准专门化,并且您应该始终滚动自己的字符串类,其构造函数只有失败而不抛出?

还是我漏掉了什么诀窍?

我认为你的主要问题是你正在做catch(...)并翻译为std::runtime_error,从而失去了原始异常的所有类型信息。你应该用throw()重新抛出。

实际上,如果你内存不足,你可能会在某个时候抛出bad_alloc异常,并且没有很多其他你可以或应该做的。如果希望因为分配失败以外的原因抛出异常,那么用有意义的上下文信息构造一个合理的异常对象可能不会有问题。如果在格式化异常对象时遇到内存问题,除了传播内存错误之外,您可以做的事情不多。

你是对的,如果你构造一个新的字符串对象来构造一个异常,这是一个潜在的问题,但如果你想用上下文格式化消息,这通常是无法避免的。请注意,标准异常对象都有一个const char*构造函数(截至上周),所以如果你想使用一个const char*,你不必构造一个新的std::string对象。

std::runtime_error必须复制它的实参,但不一定是一个新的字符串对象。可能有一个静态分配的内存区域,它可以将参数的内容放入其中。它只需要满足what()要求,这只需要返回const char *,它不需要存储std::string对象。

这将是一个throw表达式,它本身也会抛出:won't This将调用std::terminate?

不,它不会。它只会抛出关于内存不足的异常。控件将无法到达外部throw部分。

但是等等,这个也不行,不是吗?因为runtime_error是要复制它的参数,这也可能抛出…

带有抛出复制构造函数的异常类和抛出析构函数一样邪恶。对此我们无能为力。

std::runtime_error被设计用来处理常见的运行时错误,而不是内存不足或其他类似的关键异常。基类std::exception does not做任何可能抛出的事情;也没有std::bad_alloc。显然,将std::bad_alloc重新映射为需要动态分配才能工作的异常是一个坏主意。

第一件事是,如果你碰巧有一个bad_alloc异常,你想做什么,因为你的内存不足?

我想说,在一个经典的c++程序中,你会希望程序以某种方式告诉你发生了什么,然后终止。

在一个经典的c++程序中,你会让bad_alloc异常传播到程序的主部分。main函数将包含如下的try/catch语句:
int main()
{
   try
   {
      // your program starts
   }
   catch( const std::exception & e )
   {
       std::cerr << "huho something happened" << e.what() << std::endl;
   }
   catch( ... )
   {
       std::cerr << "huho..err..what?" << std::endl;
   }
}

只使用catch(…)在main内部和线程的起始函数处。与Java等其他语言相反,您不需要在本地捕获所有可能的异常。你只要让它们繁殖,直到你在你想要的地方抓住它们。

现在,如果你的代码必须检查std::bad_alloc,你应该只检查catch(const std::bad_alloc &)本地。在这种情况下,它应该做一些其他的事情,而不是仅仅抛出另一个异常。

我在c++编程语言§14.10中也发现c++异常处理机制为自己保留了一点内存来保存异常,这样抛出标准库异常就不会自己抛出异常。当然,如果您确实编写了一些错误的代码,也有可能让异常处理机制耗尽内存。

所以,总而言之,如果你什么都不做,让像bad_alloc这样的大异常在你想要捕获它们的地方很好地传播,在我看来你是安全的。你不应该使用catch(…)catch(const std::exception &)在除了main函数和线程的起始函数之外的任何地方。

捕获所有异常以重新抛出单个异常实际上是最后要做的事情。你失去了c++异常处理机制的所有优势。