今天早些时候我发现了函数try-catch块(事实上来自here)然后进行了一些研究狂欢 - 显然它们主要用于捕获由构造函数初始化列表引入的异常
无论如何,这让我想到了失败的构造者,我已经到了一个需要稍微澄清的阶段。这只是我试图了解更多关于语言的内容,所以我没有实用示例,但这里有...
给出这个示例代码:
class A
{
private:
B b
C *c; //classes B, C & D omitted for brevity as not really relevant
D d;
public
A(int x, int y, int z)
};
A::A(int x, int y, int z)
try
: b( x )
, c( new C(y) )
, d( z )
{
//omitted
}
catch(...)
{
//omitted
}
在这些情况下会发生什么:
b
的初始化会引发异常。c
的初始化会引发异常。d
的初始化会引发异常。具体来说,我想知道至少:
new C(y)
吗? 在案例1和2中是危险的吗? 显然,我想最安全的做法是让delete b
成为智能指针。但是暂时忽视这个选项,最好的做法是什么?
在初始化程序中将c
设置为c
,然后将NULL
的调用放在构造函数体中是否安全?
这就意味着必须在catch中放置new
以防其他东西抛出构造函数体?这样做是否存在安全问题(即,如果它是delete c
本身会抛出的那些?)
答案 0 :(得分:8)
尝试/捕捉功能块是不受欢迎的,就像goto一样 - 它们可能是一些有意义的角落情况,但最好避免它们:当一个对象无法构建时,你可以做的最好的事情失败并快速失败。
在具体问题上,当构造函数中抛出异常时,将销毁所有完全构造的子对象。这意味着在b
的情况下它将被销毁,在c
的情况下,它是一个原始指针,什么也没做。最简单的解决方案是将c
更改为处理已分配内存的智能指针。这样,如果d
抛出,智能指针将被销毁并释放对象。这与try / catch块无关,而是与构造函数的工作方式有关。
通常,从catch块中删除指针也是不安全的,因为在执行初始化之前无法保证指针的实际值。也就是说,如果b
或c
抛出,则c
可能不是0,但也不是有效指针,删除它将是未定义的行为。像往常一样,如果您保证b
和c
都不会抛出,并假设bad_alloc
不是您通常会从中恢复的东西,那么它可能是安全的
请注意,如果由于某些特定原因需要保持带有原始指针的指针,最好在初始化列表中将其初始化为0,然后在构造块内创建对象以避免此问题。还要记住,在类中直接保存多个(原始)资源会使确保没有泄漏资源变得非常困难 - 如果创建和分配了第一个资源,而第二个资源失败,则不会调用析构函数资源会泄漏。同样,如果你可以使用智能指针,那么你可以解决一个问题。
答案 1 :(得分:2)
除了将一个例外转换为另一个例外,你不能在function-try-block处理程序中做任何事情。你不能阻止抛出异常。你不能对班级成员做任何事情。所以不,你不能在构造函数的function-try-block中做delete c
。
完全构造的基类和 一个物体的成员应该是 在进入处理程序之前销毁 一个函数的尝试块 构造函数或析构函数 对象
和
当前处理的异常是 如果控制到达结束时重新生长 function-try-block的一个处理程序 构造函数或析构函数。 否则,函数返回时 控制到达处理程序的末尾 for function-try-block(6.6.3)。 流出一个结束 function-try-block相当于a 没有价值的回报;这导致了 在返回值中的未定义行为 功能
(来自C ++标准的15.3 [except.handle])
答案 2 :(得分:0)
要回答你的实际问题,任何时候在构造期间发生异常,执行就会在那里停止(从不构造正在构造的对象),控制传递给最近的异常处理程序(catch
)。
仅在d
导致异常时才会发生内存泄漏,在这种情况下,外部catch
需要清理。
如果new C
本身抛出,则永远不会构造该对象,因此您无需担心删除它。