C++异常处理知多少(二)

来源:互联网 发布:linux常用命令及用法 编辑:程序博客网 时间:2024/04/30 10:39

1.标准异常类定义在四个头文件中:exception,new,type_info,stdexcept。

2.exception中定义了exception类,new中定义了bad_alloc类,type_info中定义了bad_cast类,stdexcept中定义了runtime_error、logic_error类。

3.runtime_error类(表示运行时才能检测到的异常)包含了overflow_error、underflow_error、range_error几个子类;logic_error类(一般的逻辑异常)包含了domain_error、invalid_argument、out_of_range、length_error几个子类;而所有的这些类都是exception类的子类。

4.exception、bad_alloc、bad_cast类只定义了默认构造函数,无法在创建这些异常的时候提供附加信息。其它异常类则只定义了一个接受字符串的构造函数,字符串初始化式用于为所发生的异常提供更多的信息。

5.所有异常类都有一个what虚函数,它返回一个指向C风格字符串的指针。

6.应用程序可以从exception或者中间基类派生自已的异常类来扩充exception类层次。

7.异常说明跟在函数形参表之后,一个异常说明在关键字throw之后跟着一个由圆括号括住的异常类型表,如:void foo(int) throw(bad_alloc, invalid_argument);。异常列表还可以为空:void foo(int) throw();,表示该函数不抛出任何异常。

8.异常说明有用的一种重要情况是,如果函数可以保证不会抛出异常。确定函数将不抛出任何异常,对函数的使用者和对编译器都是非常有用的。知道函数不抛出异常会简化编写该函数异常安全的代码工作,而编译器则可以执行被抛出异常抑制的代码优化。

9.标准异常类中的析构函数和what虚函数都承诺不抛出异常,如what的完整声明为:virtual const char* what() const throw();。

10.派生类中的虚函数不能抛出基类虚函数中没有声明的新异常,这样在编写代码时才有一个可依赖的事实:基类中的异常列表是虚函数的派生类版本可以抛出的异常列表的超集。

11.上篇文章说过,在异常抛出栈展开的时候,编译器会适当撤销函数退出前分配的局部空间,如果局部对象是类类型,则自动调用它的析构函数。但如果在函数内单独地使用new动态的分配了内存,而且在释放资源之前发生了异常,那么栈展开时这个动态空间将不会被释放。而由类类型对象分配的资源不管是静态的还是动态的一般都会适当的被释放,因为栈展开时保证调用它们的析构函数。因此,在可能存在异常的程序以及分配资源的程序最好使用类来管理那些资源,看一个例子:
void f() {
        const int N=10;
        int* p=new int[N];
        if(...) {
                throw exception; 
        }
        delete[] p;
}
当这个异常发生时,p指向的动态空间将不会被正常撤销。现在我们用类来管理这个资源:
template <typename T>
class Resource {
private:
        unsigned int size;
        T* data;
public:
        Resource(unsigned int _size=0):size(_size),data(new T[_size]) {
                for(unsigned int i=0; i<size; ++i) {
                        data[i]=T();
                }
        }
        Resource(const Resource& r):size(r.size),data(new T[r.size]) {
                for(unsigned int i=0; i<size; ++i) {
                        data[i]=r.data[i];
                }
        }
        Resource& operator=(const Resource& r) {
                if(&r!=this) {

                        size=r.size;
                        delete[] data;
                        data=new T[size];
                        for(unsigned int i=0; i<size; ++i) {
                                data[i]=r.data[i];
                        }
                }
                return *this;
        }
        ~Resource() {delete[] data;}
        T operator[](unsigned int index);
        const T operator[](unsigned int index) const;
};
void f() {
        const int N=10;
        Resource<int> p(N);
        if(...) {
                throw exception; 
        }

}
这里即使抛出了异常,也会自动调用对象p的析构函数。
12.异常抛出栈展开的时候,编译器对局部类对象析构函数的自动运行导致了一个重要的编程技巧的出现,它使程序更为异常安全的。通过定义一个类来封装资源的分配和释放,可以保证正确的释放资源。这一技术常称为“资源分配即初始化”,简称为RAII。第11条已给出了它的用法。

原创粉丝点击