std::exception
要求其构造函数为throw()
。然而,std::runtime_error
接受std::string
作为其参数,表示它在某处存储了std::string
。因此,必须在某处进行分配或复制构造。对于std::string
,这不是nothrow
操作。
runtime_error::runtime_error
如何与throw()
见面?
(对于上下文,我正在实现一个异常类型,并希望从调用站点存储一些std::string
,我想要正确地执行它...)
答案 0 :(得分:9)
(Here's在最小化测试用例中的相同内容。)
runtime_error::runtime_error(string const&)
不需要见throw()
。
它不会继承或覆盖exception::exception()
,并且在调用string
的复制构造函数时,exception::exception()
已完成。
如果复制string
是要抛出异常,这将放松runtime_error::runtime_error(string const&)
然后,我想,调用exception::~exception()
。
很难直接表明派生的ctor不需要满足基本ctor的异常说明符,但是下面的段落强烈暗示了它(它描述了如何调用base的析构函数,而不是将异常传递给基础构造函数):
[2003: 15.2/2]
部分构建的对象或 部分被摧毁将为其所有人执行析构 完全构造的子对象,即对于其子对象的子对象 构造函数已完成执行,析构函数尚未完成 开始执行。应该是一个自动元素的构造函数 数组抛出异常,只有该数组的构造元素 将被销毁。如果对象或数组是在a中分配的 new-expression,匹配解除分配函数(3.7.3.2,5.3.4, 12.5),如果有的话,被调用来释放对象占用的存储空间。
唯一接近你假定的场景(我最初推测的场景)的道路如下:
[2003: 15.4/3]
如果虚函数有异常规范, 任何函数的所有声明,包括定义 覆盖任何派生类中的虚函数只允许 基数的异常规范所允许的异常 类虚函数。
但显然exception::exception()
不是虚函数,显然runtime_error::runtime_error(string const&)
不会覆盖它。
(请注意,此方案 将适用于虚拟析构函数;因此,you can see that, in libstdc++, runtime_error::~runtime_error()
is throw()
)。
答案 1 :(得分:6)
然而
std::runtime_error
接受std::string
作为其参数,表示它在某处存储std::string
。因此,必须在某处进行分配或复制构造。对于std::string
,这不是noexcept
操作。
runtime_error
(和logic_error
)只需要接受std::string const &
类型的参数。 他们不需要复制它。
使用这些重载会让您自担风险。 LLVM libc ++不提供存储。
另一方面,GNU libstdc ++小心翼翼地小心,以避免内存不足。它将字符串的内容复制到异常存储空间中,而不是复制到新的std::string
。
即使这样,它也会添加std::string&&
重载并使用friend
来采用rvalue传递的std::string
参数的内部缓冲区,以节省异常存储空间。
所以这是你的真实答案:“非常小心,如果有的话。”
您可以使用std::runtime_error
作为您自己的异常类的成员来利用GCC的慷慨,每个都存储一个字符串。不过,这对Clang来说仍然没用。
堆栈展开期间的异常会导致terminate
被调用。
但构造要抛出的对象不是展开的一部分,并且与throw
表达式之前的代码没有区别。
如果std::runtime_error::runtime_error( std::string const & )
抛出std::bad_alloc
,runtime_error
异常将丢失(它从未存在过),而bad_alloc
则会被处理。
对于您自己的类,从呼叫站点存储std::string
,您将要遵循§18.8.1/ 2:
从类异常派生的每个标准库类T都应具有可公开访问的副本构造函数和不能以异常退出的可公开访问的副本赋值运算符。
这是必需的,因为从堆栈复制到线程的异常存储 对异常敏感。第15.1节/ 7:
如果异常处理机制在完成对要抛出的表达式的评估之后但在捕获异常之前调用了一个通过异常退出的函数,则调用std :: terminate(15.5.1)。
所以,你应该使用shared_ptr< std::string >
或其他一些来清理之后的副本。