C++11新特性-异常处理(二)
一、栈解旋(unwinding)——不是新概念
- 异常被抛出后,从进入try块起,到异常被抛掷前(注意理解!),这期间在栈上的构造的所有对象(指的是,catch中除了除了throw之外的其他对象),都会被自动析构,析构的顺序与构造的顺序相反(很滋自然的,在我们还没有涉及到异常处理的时候,我们从C++的内存管理就知道在{}这个符号中的操作,除了malloc等操作申请堆之外,其他操作都是在栈上操作的,其实这不是新概念!)。这一过程称为栈的解旋(unwinding)。
样例代码
#include<bits/stdc++.h>
using namespace std;
class A{};
class B{};
//自定义的异常类型
class MyException {};
class Test
{
public:
Test(int a=0, int b=0)
{
this->a = a;
this->b = b;
cout << "Test 构造函数执行" << "a:" << a << " b: " << b << endl;
}
~Test()
{
cout << "Test 析构函数执行" << "a:" << a << " b: " << b << endl;
}
private:
int a;
int b;
};
//注意下面的throw的写法! 详情,请看“二、异常接口声明”
void myFunc()
throw (MyException)
{
Test t1(111,111);
Test t2(222,222);
cout << "定义了两个栈变量,异常抛出后测试栈变量的如何被析构" << endl;
throw MyException();
}
int main()
{
try
{
myFunc();
}
//catch(MyException &e) //这里不能访问异常对象
catch(MyException ) //这里不能访问异常对象
{
cout << "接收到MyException类型异常" << endl;
}
catch(...)
{
cout << "未知类型异常" << endl;
}
return 0;
}
//Test 构造函数执行a:111 b: 111
//Test 构造函数执行a:222 b: 222
//定义了两个栈变量,异常抛出后测试栈变量的如何被析构
//Test 析构函数执行a:222 b: 222
//Test 析构函数执行a:111 b: 111
//接收到MyException类型异常 二、异常接口说明(这个概念得了解,不然看不懂别人写的代码!)
- 1)为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:
void func() throw (A, B, C , D); //这个函数func()能够且只能抛出类型A B C D及其子类型的异常。
- 2)如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如:
void func(); - 3)一个不抛掷任何类型异常的函数可以声明为:
void func() throw(); - 4) 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数中止程序。
三、异常对象变量的生命周期(异常对象的内存模型)
注意 异常对象的内存模型 。
四、“继承”在异常中的应用(没有搞懂???)
异常的层次结构
- 异常是类 – 创建自己的异常类
- 异常派生
- 异常中的数据:数据成员
- 按引用传递异常
- 在异常中使用虚函数
案例:设计一个数组类 MyArray,重载[]操作,
数组初始化时,对数组的个数进行有效检查
1) index<0 抛出异常eNegative
2) index = 0 抛出异常 eZero
3)index>1000抛出异常eTooBig
4)index<10 抛出异常eTooSmall
5)eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。
五、标准程序库异常(标准的!)
Exception,n.例外,规则外的例外
异常处理代码演示
1、样例代码1(// out_of_range,表示一个参数不在允许范围之内)
#include<iostream>
#include<stdexcept>
using namespace std;
class Teacher
{
public:
Teacher(int age) //构造函数, 通过异常机制 处理错误
{
if (age > 100)
{
throw out_of_range("年龄太大");
}
this->age = age;
}
private:
int age;
};
int main()
{
try
{
Teacher t1(102);
}
catch (out_of_range e)
{
cout << "捕获下列异常:" << endl;
cout << e.what() << endl;
}
return 0;
}
//捕获下列异常:
//年龄太大 2、样例代码2(bad_alloc,用new动态分配空间失败)
#include<bits/stdc++.h>
using namespace std;
class Dog
{
public:
Dog()
{
parr = new int[1024*1024*100]; //4MB
}
private:
int *parr;
};
int main()
{
Dog *pDog;
try
{
for(int i=1; i<1024; i++) //40GB!
{
pDog = new Dog();
cout << i << ": new Dog 成功." << endl;
}
}
catch(bad_alloc err)
{
cout << "new Dog 失败: " << err.what() << endl;
}
return 0;
}
//1: new Dog 成功.
//2: new Dog 成功.
//3: new Dog 成功.
//new Dog 失败: std::bad_alloc 3、样例代码3(invalid_argument,表示向函数传入无效参数)
//exception "标准程序库异常类"的公共基类
#include<iostream>
#include<cmath>
#include<stdexcept>
using namespace std;
//x
double area(double a,double b,double c)
throw (invalid_argument)
{
if(a<=0||b<=0||c<=0)
{
throw invalid_argument("边长应该为正");
}
if( (a+b)<=c || (b+c)<=a || (a+c)<=b)
{
throw invalid_argument("边长不符合条件");
}
double s=(a+b+c)/2;
return sqrt(s*(s-a)*(s-b)*(s-c));
}
int main()
{
double a,b,c;
cout<<"输入a,b,c"<<endl;
while(cin>>a>>b>>c)
{
try
{
double s=area(a,b,c);
cout<<s<<endl;
}
catch(exception &e)
{
cout<<"Error:"<<e.what()<<endl;
}
}
return 0;
} [演示]
输入a,b,c 3 4 5 6 0 5 5 Error:边长应该为正 1 1 2 Error:边长不符合条件
六、补充,C语言(传统处理错误方式)和C++中错误处理对比
0、C语言中传统的处理错误方式
从异常处理和业务处理混合在一起
#include<bits/stdc++.h>
using namespace std;
//文件的二进制copy
int filecopy01(char *filename2, char *filename1 )
{
FILE *fp1= NULL, *fp2 = NULL;
fp1 = fopen(filename1, "rb");
if (fp1 == NULL)
{
return 1;
}
fp2 = fopen(filename2, "wb");
if (fp1 == NULL)
{
return 2;
}
char buf[256];
int readlen, writelen;
while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果读到数据,则大于0
{
writelen = fwrite(buf, 1, readlen, fp2);
if (readlen != readlen)
{
return 3;
}
}
fclose(fp1);
fclose(fp2);
return 0;
}
int main()
{
int ret;
ret = filecopy01("c:/1.txt","c:/2.txt");
if (ret !=0 )
{
switch(ret)
{
case 1:
printf("打开源文件时出错!\n");
break;
case 2:
printf("打开目标文件时出错!\n");
break;
case 3:
printf("拷贝文件时出错!\n");
break;
default:
printf("发生未知错误!\n");
break;
}
}
return 0;
} 1、C++
- C++编译器通过throw 来产生对象,C++编译器再执行对应的catch分支,相当于一个函数调用,把实参传递给形参。
//throw int类型变量
//throw 字符串类型
//throw 类类型
