为什么在析构函数中引发异常时不调用重载删除
Why is an overloaded delete not called when an exception is thrown in a destructor?
我已经编写了下面的代码,它重载了new
和delete
运算符,并在析构函数中抛出异常。
当抛出异常时,为什么delete
运算符中的代码没有执行(并打印"再见")?
如果不应该执行,(如何)释放内存?是否调用了其他delete
运算符之一?重载其中一个会导致相应的代码被执行吗?或者,内存根本没有被释放,因为失败的破坏意味着它可能不应该被释放?
#include <iostream>
using namespace std;
class A
{
public:
A() { }
~A() noexcept(false) { throw exception(); }
void* operator new (std::size_t count)
{
cout << "hi" << endl;
return ::operator new(count);
}
void operator delete (void* ptr)
{
cout << "bye" << endl;
return ::operator delete(ptr);
}
// using these (with corresponding new's) don't seem to work either
// void operator delete (void* ptr, const std::nothrow_t& tag);
// void operator delete (void* ptr, void* place);
};
int main()
{
A* a = new A();
try
{
delete a;
}
catch(...)
{
cout << "eek" << endl;
}
return 0;
}
输出:
hi
eek
现场演示。
我看了看:
- 从析构函数中抛出异常
- 当构造函数抛出异常并使用自定义new时,C++如何释放内存
- 以及其他
但我找不到确切的答案:(1)析构函数中的异常(与构造函数相反)和(2)重载删除。
我不需要关于在析构函数中抛出异常是一种糟糕做法的讲座——我只是遇到了类似的代码,我对这种行为很好奇。
如果存在标准或类似参考文献,我更希望得到该参考文献支持的答案。
标准草案N4296 5.3.5,第121页称:
[expr.delete][注意:解除分配函数无论对象或数组的某个元素的析构函数是否引发异常,都会调用。--尾注]
因此,必须在析构函数抛出的无顺序调用operator delete
。
然而,正如评论中所显示的那样,一些编译器并没有正确地调用operator delete
。这可以作为错误编译器来解决。
测试错误:
- GCC 4.8
- Visual Studio 2015
在1998年C++标准(ISO/IEC 14882第一版,1998-09-01)中,删除表达式的工作方式在第6段和第7段的"第5.3.5节删除[expr.delete]"中非常简单地说明。
6删除表达式将为要删除的对象或数组元素调用析构函数(如果有的话)。在数组的情况下,元素将按照地址递减的顺序(即其构造函数的完成顺序相反;见12.6.2)。
7删除表达式将调用解除分配函数(3.7.3.2)。
在组合中,这些子句要求将调用析构函数(或数组的析构函数),并且将无条件地调用释放函数。这里没有关于在引发异常时不调用deallocation函数的规定。
在1998年的标准中,语言律师和编译器开发人员可能会为争论与我上面所说的不同的解释而感到高兴。幸运的是,在后来的标准中,情况更加明确。。。
在open-std.org上提供的草案N4296中,相同的条款扩展如下:(据记忆,官方标准中的措辞相同,但我目前的机器上没有副本)
(强调我的)
6如果删除表达式的操作数值不是空指针值,则
的删除表达式7如果删除表达式的操作数值不是空指针值,则:
(7.1)-如果要删除的对象的新表达式的分配调用没有被省略,并且分配没有被扩展(5.3.4)delete表达式应调用释放函数(3.7.4.2)。新表达式allocation调用返回的值应作为第一个参数传递给释放函数。
(7.2)-否则,如果分配是扩展的,或者是通过扩展另一个新表达式的分配而提供的,并且对于具有由扩展的new表达式
提供的存储的new表达式产生的每一个其他指针值,delete表达式elete表达式应调用一个解除分配函数。扩展新表达式的分配调用返回的值应作为第一个参数传递给deallocation函数。(7.3)-否则,删除表达式将不会调用解除分配功能(3.7.4.2)。
否则,将不指定是否调用deallocation函数。注意:无论对象或数组的某个元素的析构函数是否抛出异常,都会调用deallocation函数。--结束注释]
结尾的注释指出,即使析构函数抛出异常,也必须调用释放函数。
我不确定是哪种标准的演变首先阐明了问题,但基于上述内容,条款可能会保留在第5.3.5节中(标签[expr.delete])。
在调用delete运算符之前调用析构函数。请参阅cppreference-删除表达式
如果表达式不是空指针,则delete表达式会为要销毁的对象或要销毁的数组的每个元素调用析构函数(如果有)(从数组的最后一个元素到第一个元素)。之后,除非匹配的新表达式与另一个新表达式组合(由于C++14),否则delete表达式将调用释放函数,即运算符delete(对于表达式的第一个版本)或运算符delete[](针对表达式的第二个版本)。
由于这种操作顺序,在调用重载版本的delete运算符之前,会调用析构函数并引发异常。
- 调用重载的"<大括号括起来的初始值设定项列表>"对于对来说就足够了是模棱两可的
- 为什么初始化时没有调用重载赋值运算符?
- 使用 MINGW gcc 编译时,不会为 std::string 调用重载的新运算符
- 为什么使用不匹配的参数调用重载函数仍然有效
- 无法弄清楚为什么没有调用重载运算符 []
- 使用 nullptr 调用重载方法是不明确的
- C++编译时检查是否可以用某种类型的参数调用重载函数
- 在 if 语句中调用重载构造函数失败
- 为什么在析构函数中引发异常时不调用重载删除
- 从派生类调用重载函数
- 传递 const int* 和 int* 时调用重载函数的不同版本(const int* const&/&&)
- C++ - 在 std::thread 中调用重载函数时编译失败
- 为在与类方法中的类相同的命名空间中定义的结构调用重载运算符
- 从 C++ 中的子类调用重载父类的方法
- 在C++中,如何从父类变量的子类调用重载方法
- 使用指针调用重载而不分配新内存
- 如何在复制构造函数中调用重载的下标
- DetectMultiScale在使用rejectLevels和levelWeights调用重载时从不返回
- 从对象指针调用重载运算符 ()
- C++ 从类中调用重载运算符