如何在不抛出的情况下构造<stdexcept>或<system_error>异常?

时间:2016-03-19 20:26:29

标签: c++ exception out-of-memory

C:\Users\<user>\AppData\Local\SceneBuilder\app\dist.jar中定义的异常(例如<stdexcept>std::logic_error及其子类(如std::runtime_error)的构造函数需要字符串参数,例如:

std::system_error

有后置条件

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; }; 的C ++ 11中的regex_error !!!

为什么这些异常没有no-throw / noexcept构造函数? C ++核心指南对此有发言权吗?

PS:就个人而言,我会在异常祖先链中留下<regex>纯粹的抽象方法。

编辑09.10.2017:这是一个PoC,证明what()构造可以抛出std::runtime_error

std::bad_alloc

4 个答案:

答案 0 :(得分:2)

简短回答,不可能构造任何绝对保证无异常的对象。

您可以考虑在堆栈上进行分配,但是您的线程堆栈可能会耗尽并导致系统错误/异常。您的CPU可能会在您抛出时获得外部中断,系统无法处理并且所有内容都会被炸毁。正如其他人所建议的那样,不要担心这些小东西。内存耗尽是大多数用户程序无法恢复的,因此不要担心它,优雅地失败。不要试图处理每一个糟糕的情况,只有你可以轻松恢复的情况。

作为旁注,对于内存不足的情况,许多高级图形游戏在游戏初始化期间预先完成所有堆分配,并在游戏开始减少遇到问题后尽量避免在游戏过程中内存不足/分配缓慢(抖动游戏和糟糕的用户体验)。您可以同样聪明地设计程序,以减少遇到不良情况的可能性。

答案 1 :(得分:2)

如果没有获得$this->session->unset_userdata("session_name"); ,您就无法构建std::logic_errorstd::runtime_error,但大部分时间它并不重要。如果操作可能失败,则无论如何都必须提供单独的代码路径来处理此问题,同样的代码路径也可用于处理std::bad_alloc。在重要的极少数情况下,您应该直接从std::bad_alloc派生,并使std::exception成员函数返回固定字符串。大多数时候&#34;函数X抛出Y&#34;,应该被读作&#34;函数X抛出Y和what()&#34;,除非另有明确说明。这也是为什么C ++ 11放弃了std::bad_alloc指定为throw()

的原因

我没有找到有用的前期异常分配,因为如果您遇到noexcept(),此时丢失一些错误信息可能是一个不错的选择,因为这种情况很少见且不值得麻烦。调用代码可以假设这样的函数由于内存分配失败而失败,这是其正常工作的一部分。

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

C ++ 11提供了使用std::bad_allocstd::throw_with_nested()嵌套异常的标准方法。不幸的是,如果你在std::rethrow_if_nested()块之外抛出异常,稍后在该异常上使用catch()将终止你的程序,所以这个设施IMO有点坏了。如果您真的关心这些问题,您可以实现自己的变体,可以同时进行显式链接和隐式链接(您需要std::rethrow_if_nested()来执行此操作)。您无法强迫外部库使用您的设施,但至少您的代码在这方面可能非常先进。

答案 2 :(得分: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";
}

答案 3 :(得分:0)

只有在尝试使用内置异常类型时,问题才出现。 虽然这个帖子中的其他一些答案都说, 在抛出和失败之间存在差异,而 在抛出不同类型的异常之间存在差异。

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

如果无法在堆上分配,则无法在堆栈上进行分配,从而无法轻松恢复。 的区别,因为 是一种从堆失败中恢复的方法。 以同样的方式,可以catch个不同的例外,因为人们可能会对它们做出不同的处理。 这样的机制,因为恢复可能非常不同。

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

然而,解决方案非常简单 - what()的{​​{1}}函数是虚拟的并且它返回最简单的类型 - {{1 }}。您可以直接从std::exception继承任何您想要的异常,并以不在堆上分配的方式实现它。例如,将消息作为类的静态成员(可能包含值的某些占位符)或仅将其作为字符串文字提供。这样你就可以确保在投掷时没有新的分配。