本文学习自 狄泰软件学院 唐佐林老师的 数据结构课程
经验:依赖于异常特性所创建出来的库是非常稳定的!
问:我们自己创建的数据结构库是不是也可以引进异常类族呢?
答:可以
有基本类型的异常(int char …等等),当然也可以抛出异常类的对象。
- 异常的类型可以使自定义类型
- 对于类类型异常的匹配依旧是至上而下严格匹配
- 复制兼容性原则在异常匹配中依然适用
- 一般而言:
- 匹配子类异常的catch放在上部
- 匹配父类异常的catch放在下部
现代C++库必然包含充要的异常类族,异常类是数据结构其他类所依赖的“基础设施”
如何设计一个异常类族?
由于我们的目的是创建一个数据结构库,针对数据结构中的代码,我们可以这样设计:
-
Exception(顶层父抽象类,不能够创建对象,用来被继承,创建以下五个异常类)
根据经验,在编写数据结构库的时候,一般而言就会出现这几种异常出来,因此对应我们也创建这五个异常
-
ArithmeticException 计算异常
-
NullPointException 空指针异常
-
IndexOutOfBoundsException 越界异常
-
NoEnoughMemoryException 内存不足异常
-
InvalidParameterException 参数错误异常
实验1 : 创建一个异常类组,作为数据库的异常类的顶层父类,并验证
exception.h
#ifndef EXCEPTION_H
#defined EXCEPTION_H
namespace DTLib
{
#define THROW_EXCEPTION(e,m) throw(e(m, __FILE__, __LINE__))
//异常类接口定义,顶层父类抽象类
class Exception
{
protected:
char* m_message;//指向一个字符串,用于说当前异常信息
char* m_location;//指向一个字符串,用于说明异常信息行号
//辅助函数 用于初始化
void init(const char* message, const char* file, int line);
public:
//构造函数重载
Exception(const char* message);
Exception(const char* file, int line);
Exception(const char* message, const char* file, int line);
//拷贝构造函数,赋值操作符重载(init函数中 涉及到堆空间的操作,则拷贝操作和赋值操作都是深拷贝)
//拷贝构造函数,赋值操作符重载 要保证每一个Exception对象内部的两个成员指针所执行的内容都应该是独立的一段堆空间,(防止多次free bug)
Exception(const Exception& e);
Exception& operator=(const Exception& e);
//返回对应的成员指针
virtual const char* message() const;
virtual const char* location() const;
//虚析构函数 = 0,意味着这是一个纯虚的析构函数,纯虚的析构函数作用只有一个:说明当前这个类是一个抽象类
//抽象类不能创建对象,Exception 类顶层的抽象父类,不能创建对象,应该有子类创建对象,但是测试Exception 类时 需要先写成virtual ~Exception();
//由子类创建,此时初步程序还没有实现子类,所以暂时 将纯虚构特征去掉,实验一下
virtual ~Exception() =0;
};
}
#endif
exception.cpp
#include "Exception"
#include <cstring> //字符串类头文件
using namespace std;//
namespace DTLib
{
void Exception::init(const char* message, const char* file, int line)
{
//m_message = message; //不能这样直接赋值,因为参数message指针指向的字符串位置不确定,有可能在堆空间、栈空间、全局数据区,所以没有办法控制
//message 所指向的外部字符串的生命周期,所以这样是不安全的,正确的做法是拷贝一份字符串出来。
m_message = strdup(message);//拷贝一份字符串到堆空间,所以此时m_message 指向堆空间
if(file != NULL)
{
char sl[16] = {0};//存储异常文件信息行号
itoa(line,sl,10);//将line 从整形转换为字符串 并存储‘’
m_location = static_cast<char*>(malloc(strlen(file) + strlen(sl) + 2));//申请空间用于存储 拼接file 和 line所对应的字符串,2用于存储 “:”和 字符串借宿符\0
m_location = strcpy(m_location,file);//字符串拷贝
m_location = strcat(m_location,":");//字符串拼接,首先拼接一个":"
m_location = strcat(m_location,sl); //形成 : 文件名:行号 的异常信息格式
}
else
{
m_message = NULL;
}
}
//重载构造函数
Exception::Exception(const char* message)
{
init(const char* message,NULL,0);
}
Exception::Exception(const char* file, int line)
{
init(NULL,const char* file, int line);
}
Exception::Exception(const char* message, const char* file, int line)
{
init(const char* message, const char* file, int line);
}
//拷贝构造函数
Exception::Exception(const Exception& e)
{
m_location = strdup(e.m_location);//拷贝一份字符串到堆空间,所以此时m_location指向堆空间
m_message = strdup(e.m_message);//拷贝一份字符串到堆空间,所以此时m_message指向堆空间
}
//赋值操作符重载
Exception& Exception::operator=(const Exception& e)
{
//确定不是自赋值
if(this != &e)
{
free(m_location);//释放原空间
free(m_message);
m_location = strdup(e.m_location);
m_message = strdup(e.m_message);
}
return *this;
}
const char* Exception::message() const
{
return m_message;
}
const char* Exception::location() const
{
return m_location;
}
Exception::~Exception()
{
free(m_location);
free(m_message);
}
}
main.cpp
#include <iostream>
#include "Exception.h"
using namespace std;
using namespace DTLib;
int main()
{
try
{
//扔出一个异常:此处扔出的异常是 Exception类的一个对象
//throw Exception("test", __FILE__, __line__);
THROW_EXCEPTION(Exception, "test")
}
catch(const Exception& e)
{
cout<< "catch(const Exception& e)" <<endl;
cout<< e.message() <<endl;
cout<< e.localtion() <<endl;
}
return 0;
}
catch(const Exception& e
test
…\DTLib\main.cpp:12
实验2 :添加子类
exception.h
#ifndef EXCEPTION_H
#defined EXCEPTION_H
namespace DTLib
{
#define THROW_EXCEPTION(e,m) throw(e(m, __FILE__, __LINE__))
//异常类接口定义,顶层父类抽象类
class Exception
{
protected:
char* m_message;//指向一个字符串,用于说当前异常信息
char* m_location;//指向一个字符串,用于说明异常信息行号
//辅助函数 用于初始化
void init(const char* message, const char* file, int line);
public:
//构造函数重载
Exception(const char* message);
Exception(const char* file, int line);
Exception(const char* message, const char* file, int line);
//拷贝构造函数,赋值操作符重载(init函数中 设计到堆空间的操作,则拷贝操作和赋值操作都是深拷贝)
//拷贝构造函数,赋值操作符重载 要保证每一个Exception对象内部的两个成员指针所执行的内容都应该是独立的一段堆空间,(防止多次free bug)
Exception(const Exception& e);
Exception& operator=(const Exception& e);
//返回对应的成员指针
virtual const char* message() const;
virtual const char* location() const;
//虚析构函数 = 0,意味着这是一个纯虚的析构函数,纯虚的析构函数作用只有一个:说明当前这个类是一个抽象类
//抽象类不能创建对象,Exception 类顶层的抽象父类,不能创建对象,应该有子类创建对象,但是测试Exception 类时 需要先写成virtual ~Exception();
//由子类穿件,此时初步程序还没有实现子类,所以暂时 将纯虚构特征去掉,实验一下
virtual ~Exception() =0;
};
//计算异常子类
class AirthmeticException : public Exception
{
public:
AirthmeticException() : Exception(0) { };
AirthmeticException(const char* message) : Exception(const char* message) { };
AirthmeticException(const char* file, int line) :Exception(const char* file, int line) { };
AirthmeticException(const char* message, const char* file, int line) : Exception(const char* message, const char* file, int line) { };
AirthmeticException(const Exception& e) : Exception(e) { };
AirthmeticException& operator=(const AirthmeticException& e)
{
Exception :: operator(e);
return *this;
}
};
}
#endif
exception.cpp 不变
main.cpp
#include <iostream>
#include "Exception.h"
using namespace std;
using namespace DTLib;
int main()
{
try
{
//扔出一个异常:此处扔出的异常是 Exception类的一个对象
//throw Exception("test", __FILE__, __line__);
THROW_EXCEPTION(AirthmeticException, "test")
}
catch(const Exception& e)
{
cout<< "catch(const Exception& e)" <<endl;
cout<< e.message() <<endl;
cout<< e.localtion() <<endl;
}
catch(const AirthmeticException & e)
{
cout<< "catch(const AirthmeticException & e)" <<endl;
cout<< e.message() <<endl;
cout<< e.localtion() <<endl;
}
return 0;
}
结果:
catch(const Exception& e)
test
…\DTLib\main.cpp:12
实验说明:
异常的匹配是自上而下的并且只匹配一次,如果catch语句块所捕获的异常类型是具有继承关系的,那么父类所对应的catch语句放在下面,子类所对应的catch语句快放在上面; 所以需要将 catch(const AirthmeticException & e) 放在 catch(const Exception& e) 之前;
- 异常类构建原则:在可复用代码框架设计时,尽量使用面向对象技术进行架构,尽量使用异常处理机制分离正常逻辑和异常逻辑
小结:
- 现代C++库必然包含充要的异常类族
- 所有库中的数据结构类都依赖于异常机制(数据结构相关的类都要依赖于异常类族中的类)
- 异常机制能够分离库中代码的正常逻辑和异常逻辑