c++析构函数需要异常处理吗?如需要实现有何要求?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++析构函数需要异常处理吗?如需要实现有何要求?相关的知识,希望对你有一定的参考价值。
是老师出的问题其中一道,老师很嚣张,说我们答不出,
哪位c++的前辈可以帮帮忙啊,小弟不胜感激
要举例子啊,麻烦了
#include <iostream>
#include <exception>
#include <string>
using namespace std;
void terminator()
cout << "uncaught exception!\n";
// abort(); 标准的terminate默认只调用该函数
exit(0);
void (*old_terminate) = set_terminate(terminator);
class foo
public:
void f()
throw string("f");
~foo()
throw string("~foo"); // 直接或间接(如调用其他函数等)抛出未决异常
// 没有异常处理代码,即所谓的“逃离了析构函数的异常”
;
int main()
try
foo a;
a.f(); // 一个异常
// 另一个, i.e. ~foo
catch (string& s)
cout << "inside main() catch (...), exception catched: " << s + "\n"; // 不能达到
析构函数中如果有异“逃离析构函数的异常”的出现,则多少说明该设计有些失败,原因如上所述,一个程序,动不动就出错退出的话,用户会满意吗?这其中当然还包括由此而引入的内存泄露等问题。
所以从设计原则上来讲,析构函数应杜绝抛出异常(destructors should never emit exceptions),但如果析构函数调用了其他可能会抛出异常的过程,则析构函数自己应该包含处理所有这些子过程可能抛出的异常的代码。
一种不实用的方法,库函数uncaught_exception()在栈回卷(即有未决异常)的时候返回true,可以依此来检测是否已有异常被抛出而且未决,以避免程序非正常退出:
#include <iostream>
#include <exception>
#include <string>
using namespace std;
void terminator()
cout << "uncaught exception!\n";
// abort(); 标准的terminate默认只调用该函数
exit(0);
void (*old_terminate) = set_terminate(terminator);
class foo
enum something_wrong = 1;
public:
void f()
throw string("f");
~foo()
// 技术上使用uncaught_exception做异常检测,弃车保帅,知其用则可
if(!uncaught_exception() && something_wrong)
throw string("~foo"); // 直接或间接(如调用其他函数等)抛出未决异常
;
int main()
try
foo a;
a.f(); // 一个异常
// 另一个, i.e. ~foo
catch (string& s)
cout << "inside main() catch (...), exception catched: " << s + "\n";
接下来就是我们的比较“实用”和“正规”的方法:析构函数要包揽所有处理可能在其执行过程中抛出的异常的代码:
#include <iostream>
#include <exception>
#include <string>
using namespace std;
void terminator()
cout << "uncaught exception!\n";
// abort(); 标准的terminate默认只调用该函数
exit(0);
void (*old_terminate) = set_terminate(terminator);
class foo
enum something_wrong = 1;
public:
void f()
throw string("f");
~foo()
try
throw string("~foo"); // 直接或间接(如调用其他函数等)抛出未决异常
catch (string& s)
cout << "inside main() catch (...), exception catched: " << s + "\n";
;
int main()
try
foo a;
a.f(); // 一个异常
// 另一个, i.e. ~foo
catch (string& s)
cout << "inside main() catch (...), exception catched: " << s + "\n";
关于更加精辟的解说和一些原则性的指引,请参考:
1、Thinking in C++ 2nd ed Vol.2
Part 1: Building Stable Systems
>>> Uncaught exceptions
>>> Exception safety
2、Effective C++ 3rd ed
>>> Item 8: Prevent exceptions from leaving destructors
一些从书里复制来的话:
From Thinking in C++:
>>> a destructor that throws an exception or causes one to be thrown is usually a sign of poor design or sloppy coding.
>>> It turns out to be practically impossible to design exception-safe code without assuming that destructors don’t throw exceptions. Don’t let destructors throw exceptions.
>>> Don’t cause exceptions in destructors
Because destructors are called in the process of throwing other exceptions, you’ll never want to throw an exception in a destructor or cause another exception to be thrown by some action you perform in the destructor. If this happens, a new exception can be thrown before the catch-clause for an existing exception is reached, which will cause a call to terminate( ).
If you call any functions inside a destructor that can throw exceptions, those calls should be within a try block in the destructor, and the destructor must handle all exceptions itself. None must escape from the destructor.
From EC++:
>>> Things to Remember
·Destructors should never emit exceptions. If functions called in a destructor may throw, the destructor should catch any exceptions, then swallow them or terminate the program.
·If class clients need to be able to react to exceptions thrown during an operation, the class should provide a regular (i.e., non-destructor) function that performs the operation. 参考技术A 不要从析构函数抛出任何异常...
1. 在栈的展开的过程中..即使有一个未解决的异常,析构也能运行.如果抛出一个异常,而此时还存在一个异常,程序会终止. 不过,C++提供了一个能力.可以确定是因为一个正常的退出或撤销函数调来执行析构函数,还是由于栈展开而执行的析构函数...<exception>头文件中声明有一个函数:uncauht_exception().如果有一个未捕获的异常,而且你正处在栈展开的过程中,它就会返回TRUE...否则,此函数返回FALSE,不过.此方法很混乱...应避免........这应该是你的老师说的如果实现时的要求吧.
2.客户不会显式地调用析构函数,它们会调用delete,而delete会调用析构函数...如果从析构函数抛出一个异常...那不能再对对象调用delete了....也不能显式地调用析构函数了.....所以...根本不应当为此代码加上异常处理的负担.
3.析构函数是释放对象中所用内存和资源的一个机会...如果浪费了这个机会...由于出现异常而过早地退出函数....就无法回收和释放内在或资源了....所以...要非常小心....对析构函数中所做调用可能抛出的任何异常...都必须在析构函数中捕获....正常情况下,析构函数只能调用delete和delete[]..而它们不能抛出任何异常......因此...这不算大问题. 参考技术B 没必要
你们老师可能是看见某个地方写了什么东西(比如自认为比较好的书上写的),一时自信心膨胀,问这么个鬼问题
一般的软件开发中都不会使用很别扭的东西,风险大,不易修改
鄙视他一下就行了,别放在心上 参考技术C delete 〔〕p;
而在释放时发生异常,则这段内存没有被释放掉,造成内存泄漏,因此就要对这个释放做异常处理:
try
delete []p;
...
在某个地方捕获这个异常,重新进行释放内存处理
事实上...
要杜绝的构造函数里处理异常本回答被提问者采纳 参考技术D 理论上...
当你的类中要分配动态内存里就需要在析构函数中进行异常处理
假如在析构函数中要释放一段内存如下:
delete 〔〕p;
而在释放时发生异常,则这段内存没有被释放掉,造成内存泄漏,因此就要对这个释放做异常处理:
try
delete []p;
...
在某个地方捕获这个异常,重新进行释放内存处理
事实上...
要杜绝的构造函数里处理异常
C++中的纯虚析构函数
【中文标题】C++中的纯虚析构函数【英文标题】:Pure virtual destructor in C++ 【发布时间】:2010-10-12 11:17:33 【问题描述】:写错了吗:
class A
public:
virtual ~A() = 0;
;
对于抽象基类?
至少在 MSVC 中编译...它会在运行时崩溃吗?
【问题讨论】:
它可以编译,但它是否链接? 【参考方案1】:是的。你还需要实现析构函数:
class A
public:
virtual ~A() = 0;
;
inline A::~A()
应该足够了。
既然这得到了反对票,我应该澄清一下:如果你从 A 派生任何东西然后尝试删除或销毁它,A
的析构函数最终将被调用。由于它是纯粹的并且没有实现,因此会出现未定义的行为。在一个流行的平台上,这将调用 purecall 处理程序并崩溃。
编辑:将声明修复为更符合,使用http://www.comeaucomputing.com/tryitout/编译
【讨论】:
嗯,是的。纯只是意味着派生类也需要提供实现。 实现纯虚函数其实是合法的。对于提供默认实现但强制子类显式调用它非常有用。 MSN 并注意如果您在标题中有该定义,则需要在其前面放置“内联”以避免违反 ODR(一个定义规则) 为什么 A::~A() 必须明确定义,因为我认为每个对象都有一个默认析构函数?与任何类型的继承一样,总是调用析构函数链,而不必总是定义基类析构函数。 更好的说法是,一旦你声明了析构函数,它就不会自动为你实现。【参考方案2】:私有析构函数:当您创建派生类的对象时,它们会给您一个错误——否则不会。不过可能会出现诊断信息。
12.4 析构函数
6 析构函数可以声明为虚拟(10.3)或纯虚拟(10.4);如果在程序中创建了该类或任何派生类的任何对象,则应定义析构函数。
具有纯虚析构函数的类是抽象类。 请注意:
10.4 抽象类
2 纯虚函数仅在使用或如同使用 (12.4) 限定 ID 语法 (5.1) 调用时才需要定义。
[注意:函数声明不能同时提供纯说明符和定义——结束 注意]
直接取自草稿:
struct C
virtual void f() = 0 ; // ill-formed
;
【讨论】:
+1。我认为 Herb Sutter 对此也有一些很好的信息:gotw.ca/gotw/031.htm。有趣的是,任何纯虚函数都可能提供了一个实现,而不仅仅是析构函数。 是的,这是你在面试中用来吓唬你的面试官的事情;) 根据我的经验,这实际上并不少见。 @Neil Butterworth:哪一个? @Dirk - “任何功能”场景。发现它用于实现一些常见行为的情况并不少见。以上是关于c++析构函数需要异常处理吗?如需要实现有何要求?的主要内容,如果未能解决你的问题,请参考以下文章