17

std::exception要求它的构造函数是throw(). 然而,std::runtime_error接受 astd::string作为它的参数,这表明它在std::string某处存储 a。因此,必须在某处进行分配或复制构造。而对于std::string,这不是一个nothrow操作。

那怎么会runtime_error::runtime_errorthrow()

(对于上下文,我正在实现一个异常类型,并且想std::string从调用站点存储几个 s,并且我想正确地做到这一点......)

4

2 回答 2

9

这是一个最小的测试用例中的相同内容。)


runtime_error::runtime_error(string const&)不需要见面throw()

它不会继承或覆盖exception::exception(),并且在string调用 的复制构造函数时exception::exception()已经完成。

如果复制string要抛出异常,这将展开runtime_error::runtime_error(string const&),然后,我想,调用exception::~exception().


很难直接表明派生 ctor 不需要满足基 ctor 的异常说明符,但以下段落强烈暗示了这一点(描述了如何调用基的析构函数,而不是将异常传递给基构造函数):

[2003: 15.2/2] 部分构造或部分销毁的对象将为其所有完全构造的子对象执行析构函数,即构造函数已完成执行且析构函数尚未开始执行的子对象。如果自动数组元素的构造函数抛出异常,则只有该数组的构造元素将被销毁。如果对象或数组是在 new 表达式中分配的,则调用匹配的释放函数(3.7.3.2、5.3.4、12.5)(如果有)以释放对象占用的存储空间。

唯一接近你假设(我最初假设)的场景的段落如下。

[2003: 15.4/3]如果虚函数具有异常规范,则在任何派生类中覆盖该虚函数的任何函数的所有声明(包括定义)都应仅允许基类虚函数的异常规范允许的异常。

但显然exception::exception()不是一个虚函数,而且显然runtime_error::runtime_error(string const&)没有覆盖它。

(请注意,这种情况适用于虚拟析构函数;因此,您可以在 libstdc++ 中看到runtime_error::~runtime_error()isthrow())。

于 2011-07-28T18:50:58.733 回答
7

2015 年更新:

然而,std::runtime_error接受 astd::string作为它的参数,这表明它在std::string某处存储 a。因此,必须在某处进行分配或复制构造。而对于std::string,这不是一个noexcept操作。

runtime_error(and logic_error) 只需要接受类型为 的参数std::string const &他们不需要复制它。

使用这些重载需要您自担风险。LLVM libc++ 不提供存储。

另一方面,GNU libstdc++ 小心翼翼地避免内存不足。它复制字符串的内容,但复制到异常存储空间,而不是新的std::string.

即使这样,它也会增加一个std::string&&重载并使用friendship 来采用由 rvalue 传递的参数的内部缓冲区std::string,以节省异常存储空间。

所以这就是你真正的答案:“非常小心,如果有的话。”

std::runtime_error您可以通过使用s 作为您自己的异常类的成员来利用 GCC 的慷慨,每个都存储一个字符串。不过,这在 Clang 上仍然没有用。


原始答案,2011 年。这仍然是正确的:

堆栈展开期间的异常导致terminate调用。

但是构造要抛出的对象不是展开的一部分,并且与throw表达式之前的代码没有区别。

如果std::runtime_error::runtime_error( std::string const & )throws std::bad_allocruntime_error异常将丢失(它从未存在过)并被bad_alloc处理。

演示:http: //ideone.com/QYPj3

至于您自己的类std::string从调用站点存储 s,您需要遵循 §18.8.1/2:

从类异常派生的每个标准库类 T 都应具有可公开访问的复制构造函数和可公开访问的复制赋值运算符,它们不会因异常而退出。

这是必需的,因为从堆栈复制到线程的异常存储对异常敏感。§15.1/7:

如果异常处理机制在完成对要抛出的表达式的求值之后但在捕获异常之前调用通过异常退出的函数,则调用 std::terminate (15.5.1)。

所以,你应该在第一个之后shared_ptr< std::string >使用一个或一些这样的来清理副本。

于 2011-07-28T19:44:57.740 回答