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

jot*_*tik 6 c++ exception out-of-memory

<stdexcept>(例如std::logic_error,std::runtime_error及其子类中定义的异常std::system_error)具有构造函数,期望字符串参数,例如:

domain_error(const string& what_arg);
domain_error(const char* what_arg);
Run Code Online (Sandbox Code Playgroud)

有后置条件

strcmp(what(), what_arg.c_str()) == 0
strcmp(what(), what_arg) == 0
Run Code Online (Sandbox Code Playgroud)

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

if (haveError)
    throw std::runtime_error("BOO!"); // May throw std::bad_alloc instead?!
Run Code Online (Sandbox Code Playgroud)

而事先在其他地方构建例外似乎更安全,例如:

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;
};
Run Code Online (Sandbox Code Playgroud)

这使得我非常谨慎地抛出任何此类异常的库,例如即使regex_error<regex>在C++ 11中也是如此!

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

PS:就个人而言,我会what()在异常祖先链中留下一个纯粹的抽象方法.

编辑09.10.2017:这是一个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);
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*s K 0

if (haveError)
    throw std::runtime_error("BOO!"); // May throw std::bad_alloc instead?!
Run Code Online (Sandbox Code Playgroud)

当你到达这里的时候,throw你应该已经完成​​了所有的清理工作,所以在大多数情况下,用 throwstd::bad_alloc代替 a 并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";
}
Run Code Online (Sandbox Code Playgroud)

在这种特殊情况下,分配内存失败将导致detectFace抛出std::bad_alloc异常,这将导致灾难性的崩溃。正如您所建议的,在抛出之前首先分配异常根本不会改变任何内容 - 程序仍然会崩溃,std::bad_alloc因为分配仍然会失败。解决这个问题的唯一方法是简单地捕获std::bad_alloc

catch (std::bad_alloc& e) {
  std::cout << "bad alloc reported\n";
}
Run Code Online (Sandbox Code Playgroud)