我最近的任务是在代码的一部分中查找内存泄漏。泄漏最终出现在特定对象的析构函数中......我发现了一些非常奇怪的东西。一位前同事写道:
File::~File()
try
{
Clear();
}
catch (...)
{
Log("caught exception");
}
文件类继承自一些基类。我的第一个问题是:这是严格合法的C ++吗?它在Visual Studio 2008中编译,但我向几个朋友/同事展示了它们,并且它们相当惊恐,它的工作原理。
它实际上并没有按预期工作:这个对象继承的基类有一个现在永远不会被调用的析构函数(相反如果你只是将析构函数包装在常规方法块中,那么使用try /作为该方法的一部分抓住。)
任何人都可以尝试解释为什么允许这样做,以及为什么没有调用基类析构函数?这里的析构函数并没有抛出。
答案 0 :(得分:10)
这是一个函数try块,它完全合法。
例如,请参阅here。
你可以在一个函数中执行某些操作的唯一一次你在一个函数中的普通try块中无法做的事情就是捕获构造函数初始化列表中表达式引发的异常(甚至你最终还是要这样做抛出某些东西),但这不适用于此。
这个GOTW #66特别有趣,尽管它更多地集中在构造函数上。它包含这种“道德”:
由于析构函数永远不会发出异常,因此析构函数function-try-blocks根本没有实际用途。
只是为了补充说明,编写的代码将导致任何因ISO / IEC 14882:2003 15.3 [except.handle] / 16:
而被重新引发的异常。如果控件到达构造函数或析构函数的 function-try-block 的处理程序的末尾,则会重新执行正在处理的异常。 [...]
然而,在函数try块的处理程序中为析构函数设置无参数return
是合法的 - 它只在构造函数的函数try块中被禁止 - 这将抑制异常的重新抛出。因此,这些替代方案中的任何一个都可以防止异常离开析构函数。
File::~File()
try
{
Clear();
}
catch (...)
{
Log("caught exception");
return;
}
File::~File()
{
try
{
Clear();
}
catch (...)
{
Log("caught exception");
}
}
答案 1 :(得分:4)
回答第二部分,“为什么没有调用基类析构函数?”,12.4 / 6:
执行后的身体 析构者并摧毁任何人 在。中分配的自动对象 body,类X调用的析构函数 X的直接析构函数 成员,X的析构者 直接基类...... a中的返回声明(6.6.3) 析构函数可能不会直接返回 给来电者;在转移之前 控制到调用者,析构函数 成员和基地被称为。
这并不是说析构函数抛出时会调用成员和基本析构函数。但是,15.2 / 2说:
部分对象 建造或部分毁坏 将为所有人执行析构函数 完全构建的子对象,
我认为,由于从析构函数体抛出异常,或者因为析构函数的try块抛出异常,对象是否“部分被破坏”应该是真的。我很确定“在析构函数的主体之后”应该也意味着在函数try块之后。
显然微软不同意,并且由于函数try块它没有生成“析构函数体”,并且没有完成执行“析构函数体”后发生的事情。
这对我来说听起来不对。 GCC 4.3.4确实执行基类析构函数,无论派生类dtor函数是否尝试块抛出。在它抛出的情况下,在执行catch子句之前基础被破坏。