2016-03-19 62 views
6

例外具有构造期望字符串参数,例如:如何构造一个<stdexcept>或<system_error>异常而不抛出?在<code><stdexcept></code>(例如<code>std::logic_error</code>,<code>std::runtime_error</code>及其子类,如<code>std::system_error</code>)中所定义

domain_error(const string& what_arg); 
domain_error(const char* what_arg); 

与后置条件

strcmp(what(), what_arg.c_str()) == 0 
strcmp(what(), what_arg) == 0 

分别。没有要求传递给构造函数的参数在这些例外的生命周期内保持有效,因此确保后置条件的唯一方法是重复并存储这些动态字符串。这需要记忆力,所以我假设他们的构造本身可能会抛出std::bad_alloc或类似的东西,这通常是最意想不到的。这会导致问题,因为我已经在野外看到的每一个代码示例鼓励人们写代码像

if (haveError) 
    throw std::runtime_error("BOO!"); // May throw std::bad_alloc instead?! 

,而它似乎要安全得多预先构建的例外在其他一些地方,如:

struct A { 
    // During allocation of A one would often expect std::bad_alloc anyway: 
    A() : m_someException("BOO!") {} 
    void f() { 
     /* Do stuff */ 
     if (haveError) 
      throw m_someException; 
      /* Note that according to §18.8.1.2 all standard library 
       classes deriving from `std::exception` must have publicly 
       accessible copy constructors and copy assignment operators 
       that do not exit with an exception. In implementations such 
       exception instances most likely share the common string 
       with all their copies. */ 
    } 
    std::runtime_error const m_someException; 
}; 

这使我非常谨慎的库,抛出任何这样的例外,例如,甚至regex_error<regex>在C + + 11!

为什么这些例外不具有no-throw/noexcept构造函数? C++核心指南是否对此有发言权?

PS:就我个人而言,我会在异常血统链的这一点上留下what()一个纯粹的抽象方法。

编辑2017年9月10日:这里的PoC证明std::runtime_error建设可以抛出一个std::bad_alloc代替:

#include <cstddef> 
#include <cstdlib> 
#include <new> 
#include <stdexcept> 
#include <string> 

bool throwOnAllocate = false; 

void * operator new(std::size_t size) { 
    if (!throwOnAllocate) 
     if (void * const r = std::malloc(size)) 
      return r; 
    throw std::bad_alloc(); 
} 

void operator delete(void * ptr) { std::free(ptr); } 

int main() { 
    std::string const errorMessage("OH NOEZ! =("); 
    throwOnAllocate = true; 
    throw std::runtime_error(errorMessage); 
} 
+0

是的,它可能是一个函数不会执行,因为在那个瞬间缺少未使用的堆内存,但显示错误'alert'实际上会冻结或崩溃系统。谢天谢地,这很少见。 –

+4

“,而在其他地方事先构建例外似乎更安全” - 我不明白。如果你的'throw std :: runtime_error(“BOO!”);'由于内存不足而抛出了一个不同的异常,事先分配'std :: runtime_error'仍然会*要求内存可用。我看到的唯一变化是'bad_alloc'会在之前被抛出。 – hvd

+0

@ hvd早些时候在一些更好的或**预期的**上下文中,例如'新的A()'。在错误/异常处理(包括C++ 11异常嵌套上下文)期间,意外地发生异常**被抛出是非常糟糕的。 – jotik

回答

0
if (haveError) 
    throw std::runtime_error("BOO!"); // May throw std::bad_alloc instead?! 

你到达所有清洁的这throw你应该已经照顾的时间因此在大多数情况下,抛出std::bad_alloc而不是std::runtime_error实际上并没有太大区别。

唯一例外的情况下,我可以的事情是,当异常被用来控制程序的流程 - 我倾向于这样做非常频繁的代码,如:

try { 
    auto face = detectFace(); // may throw a custom no_face_exception or a many_faces_exception 
    // do work with face 
} 
catch (no_face_exception& e) { 
    std::cout << "no face detected\n"; 
} 
catch (many_faces_exception& e) { 
    std::cout << "too many faces detected\n"; 
} 

在这种特殊情况下的故障分配内存将导致detectFace抛出一个std::bad_alloc,而不会导致灾难性的崩溃。正如你所建议的那样,首先在抛出之前分配异常不会改变任何东西 - 程序仍然会崩溃,因为分配仍然会失败,所以std::bad_alloc。解决这个问题的唯一办法是简单地赶上std::bad_alloc

catch (std::bad_alloc& e) { 
    std::cout << "bad alloc reported\n"; 
} 
2

简短的回答,这是不可能构建任何物体,没有例外的绝对保证。

你可能会考虑在堆栈上分配,但是你的线程堆栈可能会用完,并会导致系统错误/异常。你的CPU可能会得到一个外部中断,当你扔掉,系统无法处理,一切都会被炸掉。正如其他人所建议的,不要担心小事。内存不足是大多数用户程序无法恢复的内容,所以不要担心,优雅地失败。不要试图处理所有不好的情况,只要你可以轻易恢复。

作为一个侧面提示,对于内存耗尽的情况,许多高级图形游戏会在游戏初始化时预先分配所有的堆分配,并在游戏开始后尽量避免出现问题以避免遇到问题在游戏中间内存不足/缓慢分配(紧张的游戏&糟糕的用户体验)。你可以同样聪明地设计你的程序,以减少遇到不好情况的机会。

+0

感谢您提供有关预分配内存的提示。 +1 – jotik

+0

这是可能的。堆栈溢出是一种非常特殊的情况,因为除非使用递归算法,否则程序将需要固定数量的堆栈空间,无论其输入大小如何。对于动态内存分配,这是不同的,因为通常更多的输入需要更多的内存来处理它。如果输入的数据量足够大,大多数程序都会遇到OOM。 – Ivan

2

你不能构造std::logic_errorstd::runtime_error而没有得到std::bad_alloc,但大部分时间没关系。如果操作失败,则必须提供单独的代码路径来处理此问题,同样的代码路径也可以用于处理std::bad_alloc。在极少数情况下,您应该直接从std::exception派生出来,并使what()成员函数返回一个固定字符串。除非另有明确规定,否则大部分时间“函数X引发Y”应被视为“函数X引发Y和std::bad_alloc”。这也是为什么C++ 11丢弃了throw(),指定了noexcept()

我没有发现预先分配的例外是有帮助的,因为如果您遇到std::bad_alloc,在这一点上丢失一些错误信息可能是一个不错的选择,因为这种情况很少见,也不值得麻烦。调用代码可以假设这种功能由于内存分配失败而失败,这是其正常工作的一部分。

现在,如果您担心在处理另一个异常期间由于异常而丢失错误信息,您可以尝试查看异常链接(或异常嵌套)。这方面的例子可以在其他语言中找到:

C++ 11提供了一个std::throw_with_nested()异常嵌套标准方式d std::rethrow_if_nested()。不幸的是,如果抛出catch()块以外的异常,那么使用std::rethrow_if_nested()后面的异常将会终止你的程序,所以这个设施的IMO有点破碎。如果你真的关心这样的问题,你可以实现你自己的变体,可以做明确和隐含的链接(你需要std::current_exception()来做到这一点)。您将无法强制外部库使用您的设施,但至少您的代码在这方面可以非常先进。

0

只有当您尝试使用内置的异常类型时,您的问题才是真实的。 虽然在这个线程中还有其他一些答案,投掷与失败的区别,是抛出不同类型的异常之间的区别。

我该怎么说?因为这是异常机制是如何构建的。

未能在堆栈上分配不提供简单的恢复,而只是抛出堆分配失败。有有区别,因为一种从堆故障中恢复的方法。 以同样的方式,可以有不同的例外,因为可能会有不同的处理方式。有这样的机制,因为恢复可能会非常不同。

此外,虽然bad_alloc可能不是在大多数平台和应用非常普遍,它东西,一个人应该照顾,尤其是在那些情况下,内存小和/或应用程序需要很多的钱。是的,从bad_alloc恢复有时可能会比从其他问题恢复,如数据库错误或连接问题有所不同。确实如此,如果您在尝试为一个非常短的消息分配足够空间时得到bad_alloc,那么存在一个深层问题,但仍然可能存在恢复选项(例如:如果您持有大量用于缓存的数据只有目的,你可以释放它们)。

尽管如此,解决方案很简单 - 这是不是巧合的std::exceptionwhat()功能是虚拟的,并返回最简单的类型 - const char *。你可以直接从std::exception继承你想要的任何异常,并且以不在堆上分配的方式实现它。例如,将消息作为类的静态成员(可能带有一些值的占位符),或者将其作为字符串文字提供。这样你就可以确保在投掷时不会有新的分配。

相关问题