第3课 - 异常类构建

本篇开始正式进入数据结构的相关内容,目标是基于C++语言,设计一个可复用的数据结构类库DTLib。

 

1. C++异常类简介

 - C++异常的类型可以是自定义类类型

 - catch语句对于类类型异常的匹配,依旧是自上而下严格匹配

 - 赋值兼容性原则(在出现父类对象的地方,可以用一个子类对象来代替)在异常类型匹配中依然适用

 - 匹配子类异常的catch放在上面

 - 匹配父类异常的catch放在下面

2. 异常类设计

现代C++库必然包含充要的异常类族,异常类是数据结构类所依赖的“基础设施”

【异常类组成】

DTLib中的异常类由1个顶层父类和5个异常子类组成,其UML类图如下。

 【异常类功能定义】

3. 顶层父类Exception设计

【接口设计】

 1 class Exception
 2 {
 3 protected:
 4     char *m_message;   //抛异常时的提示消息
 5     char *m_location;  //抛异常的位置,格式为“file:line”
 6 
 7     //私有辅助函数,用于简化三个构造函数实现
 8     void init(const char *message, const char *file, int line);
 9 public:
10     Exception(const char *message);
11     Exception(const char *file, int line);
12     Exception(const char *message, const char *file, int line);
13 
14     Exception(const Exception &e);
15     Exception &operator = (const Exception &e);
16 
17     /*在处理异常时,用来获取异常消息和异常发生位置*/
18     virtual const char *message() const;
19     virtual const char *location() const;
20 
21     virtual ~Exception() = 0;
22 };

【构造函数实现】

1. 先来实现init(),之所以设计这个私有函数,是因为重载的三个构造函数内部逻辑是差不多的,所以先在init()中完成该逻辑,然后分别在三个构造函数中调用,可以减小冗余代码。

 1 void Exception::init(const char *message, const char *file, int line)
 2 {
 3     m_message = (message ? strdup(message) : NULL); 
4
5 if (file != NULL) 6 { 7 char sline[16] = {0}; 8 9 itoa(line, sline, 10); //将行号转换为字符串 10 11 /*动态申请内存,拷贝文件名,并拼接上行号,最终组成m_loction*/ 12 m_location = static_cast<char *>(malloc(strlen(file) + strlen(sline) + 2)); 13 m_location = strcpy(m_location, file); 14 m_location = strcat(m_location, ":"); 15 m_location = strcat(m_location, sline); 16 } 17 else 18 { 19 m_location = NULL; 20 } 21 }

注意:参数message指向的字符串可能位于堆、栈或其他地方,如果代码第3行直接用m_message = message,会导致DTLib库无法控制字符串的生命周期,这是不安全的,因此使用strdup在库内部单独拷贝保存一份。

 2. 接下来实现三个构造函数接口,可以看到有了init()之后,这三个函数的实现都变得非常简单了。

 1 Exception::Exception(const char *message)
 2 {
 3     init(message, NULL, 0);
 4 }
 5 
 6 Exception::Exception(const char *file, int line)
 7 {
 8     init(NULL, file, line);
 9 }
10 
11 Exception::Exception(const char *message, const char *file, int line)
12 {
13     init(message, file, line);
14 }

 【拷贝构造和赋值操作符重载实现】

从init()可以看出,构造异常类对象时在堆空间动态申请了内存,说明拷贝构造和赋值操作内部必须实现为深拷贝,而默认情况下它们都是浅拷贝,需要自行实现拷贝构造函数和重载赋值操作符。

 1 Exception::Exception(const Exception &e)
 2 {
 3     m_message = strdup(e.m_message);
 4     m_location = strdup(e.m_location);
 5 }
 6 
 7 Exception &Exception::operator = (const Exception &e)
 8 {
 9     if (this != &e)
10     {
11         free(m_message);
12         free(m_location);
13 
14         m_message = strdup(e.m_message);
15         m_location = strdup(e.m_location);
16     }
17 
18     return *this;
19 }

【异常消息和异常位置获取函数实现】

这两个函数的实现非常简单,直接将m_message和m_location返回就可以了。

1 const char *Exception::message() const
2 {
3     return m_message;
4 }
5 
6 const char *Exception::location() const
7 {
8     return m_location;
9 }

【析构函数实现】

看到这里大家可能会有疑问,既然析构函数被设计为纯虚函数,而纯虚函数的实现应该在子类中完成,为什么还要在这里提供纯虚函数的函数体呢?

其实,这里是一个例外,C++规定:只要自定义了析构函数,不管析构函数是不是纯虚函数,都必须提供实现。否则其子类对象析构时,会因为找不到父类析构函数的实现而导致析构失败。

1 Exception::~Exception()
2 {
3     free(m_message);
4     free(m_location);
5 }

4. 异常子类实现

5个异常子类均继承自Exception,主要实现几个构造函数、拷贝构造函数和赋值操作符重载函数。

 1 class ArithmeticException : public Exception
 2 {
 3 public:
 4     ArithmeticException() : Exception(0) { }
 5     ArithmeticException(const char *message) : Exception(message) { }
 6     ArithmeticException(const char *file, int line) : Exception(file, line) { }
 7     ArithmeticException(const char *message, const char *file, int line) : Exception(message, file, line) { }
 8 
 9     ArithmeticException(const ArithmeticException& e) : Exception(e) { }
10     ArithmeticException &operator = (const ArithmeticException &e)
11     {
12         Exception::operator =(e);
13         return *this;
14     }
15 };
16 
17 class NullPointerException : public Exception
18 {
19 public:
20     NullPointerException() : Exception(0) { }
21     NullPointerException(const char *message) : Exception(message) { }
22     NullPointerException(const char *file, int line) : Exception(file, line) { }
23     NullPointerException(const char *message, const char *file, int line) : Exception(message, file, line) { }
24 
25     NullPointerException(const NullPointerException& e) : Exception(e) { }
26     NullPointerException &operator = (const NullPointerException &e)
27     {
28         Exception::operator =(e);
29         return *this;
30     }
31 };
32 
33 class IndexOutOfBoundsException : public Exception
34 {
35 public:
36     IndexOutOfBoundsException() : Exception(0) { }
37     IndexOutOfBoundsException(const char *message) : Exception(message) { }
38     IndexOutOfBoundsException(const char *file, int line) : Exception(file, line) { }
39     IndexOutOfBoundsException(const char *message, const char *file, int line) : Exception(message, file, line) { }
40 
41     IndexOutOfBoundsException(const IndexOutOfBoundsException& e) : Exception(e) { }
42     IndexOutOfBoundsException &operator = (const IndexOutOfBoundsException &e)
43     {
44         Exception::operator =(e);
45         return *this;
46     }
47 };
48 
49 class NoEnoughMemoryException : public Exception
50 {
51 public:
52     NoEnoughMemoryException() : Exception(0) { }
53     NoEnoughMemoryException(const char *message) : Exception(message) { }
54     NoEnoughMemoryException(const char *file, int line) : Exception(file, line) { }
55     NoEnoughMemoryException(const char *message, const char *file, int line) : Exception(message, file, line) { }
56 
57     NoEnoughMemoryException(const NoEnoughMemoryException& e) : Exception(e) { }
58     NoEnoughMemoryException &operator = (const NoEnoughMemoryException &e)
59     {
60         Exception::operator =(e);
61         return *this;
62     }
63 };
64 
65 class InvalidParameterException : public Exception
66 {
67 public:
68     InvalidParameterException() : Exception(0) { }
69     InvalidParameterException(const char *message) : Exception(message) { }
70     InvalidParameterException(const char *file, int line) : Exception(file, line) { }
71     InvalidParameterException(const char *message, const char *file, int line) : Exception(message, file, line) { }
72 
73     InvalidParameterException(const InvalidParameterException& e) : Exception(e) { }
74     InvalidParameterException &operator = (const InvalidParameterException &e)
75     {
76         Exception::operator =(e);
77         return *this;
78     }
79 };

5. 抛异常操作简化设计

至此,DTLib异常类构建基本全部完成,按照我们的设计,在抛异常时的代码是这么写的: throw Exception("Exception Test", __FILE__, __LINE__);

如果在每一个需要抛异常的地方都这么写,会比较繁琐,而且代码有冗余,看起来也不太美观,因此设计下面这个宏来简化抛异常的操作。

1 //抛异常,e为异常类类型,m为提示消息字符串
2 #define THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))

这样一来,上面的抛异常操作就简化为 THROW_EXCEPTION(Exception, "Exception Test"); 

6. 异常类完整源代码

最后,给出DTLib异常类的完整源代码。

【Exception.h】

  1 #ifndef EXCEPTION_H
  2 #define EXCEPTION_H
  3 
  4 namespace DTLib
  5 {
  6 
  7 //抛异常,e为异常类类型,m为提示消息字符串
  8 #define THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))
  9 
 10 class Exception
 11 {
 12 protected:
 13     char *m_message;   //抛异常时的提示消息
 14     char *m_location;  //抛异常的位置,格式为“file:line”
 15 
 16     //私有辅助函数,用于简化三个构造函数实现
 17     void init(const char *message, const char *file, int line);
 18 public:
 19     Exception(const char *message);
 20     Exception(const char *file, int line);
 21     Exception(const char *message, const char *file, int line);
 22 
 23     Exception(const Exception &e);
 24     Exception &operator = (const Exception &e);
 25 
 26     /*在处理异常时,用来获取异常消息和异常发生位置*/
 27     virtual const char *message() const;
 28     virtual const char *location() const;
 29 
 30     virtual ~Exception() = 0;
 31 };
 32 
 33 class ArithmeticException : public Exception
 34 {
 35 public:
 36     ArithmeticException() : Exception(0) { }
 37     ArithmeticException(const char *message) : Exception(message) { }
 38     ArithmeticException(const char *file, int line) : Exception(file, line) { }
 39     ArithmeticException(const char *message, const char *file, int line) : Exception(message, file, line) { }
 40 
 41     ArithmeticException(const ArithmeticException& e) : Exception(e) { }
 42     ArithmeticException &operator = (const ArithmeticException &e)
 43     {
 44         Exception::operator =(e);
 45         return *this;
 46     }
 47 };
 48 
 49 class NullPointerException : public Exception
 50 {
 51 public:
 52     NullPointerException() : Exception(0) { }
 53     NullPointerException(const char *message) : Exception(message) { }
 54     NullPointerException(const char *file, int line) : Exception(file, line) { }
 55     NullPointerException(const char *message, const char *file, int line) : Exception(message, file, line) { }
 56 
 57     NullPointerException(const NullPointerException& e) : Exception(e) { }
 58     NullPointerException &operator = (const NullPointerException &e)
 59     {
 60         Exception::operator =(e);
 61         return *this;
 62     }
 63 };
 64 
 65 class IndexOutOfBoundsException : public Exception
 66 {
 67 public:
 68     IndexOutOfBoundsException() : Exception(0) { }
 69     IndexOutOfBoundsException(const char *message) : Exception(message) { }
 70     IndexOutOfBoundsException(const char *file, int line) : Exception(file, line) { }
 71     IndexOutOfBoundsException(const char *message, const char *file, int line) : Exception(message, file, line) { }
 72 
 73     IndexOutOfBoundsException(const IndexOutOfBoundsException& e) : Exception(e) { }
 74     IndexOutOfBoundsException &operator = (const IndexOutOfBoundsException &e)
 75     {
 76         Exception::operator =(e);
 77         return *this;
 78     }
 79 };
 80 
 81 class NoEnoughMemoryException : public Exception
 82 {
 83 public:
 84     NoEnoughMemoryException() : Exception(0) { }
 85     NoEnoughMemoryException(const char *message) : Exception(message) { }
 86     NoEnoughMemoryException(const char *file, int line) : Exception(file, line) { }
 87     NoEnoughMemoryException(const char *message, const char *file, int line) : Exception(message, file, line) { }
 88 
 89     NoEnoughMemoryException(const NoEnoughMemoryException& e) : Exception(e) { }
 90     NoEnoughMemoryException &operator = (const NoEnoughMemoryException &e)
 91     {
 92         Exception::operator =(e);
 93         return *this;
 94     }
 95 };
 96 
 97 class InvalidParameterException : public Exception
 98 {
 99 public:
100     InvalidParameterException() : Exception(0) { }
101     InvalidParameterException(const char *message) : Exception(message) { }
102     InvalidParameterException(const char *file, int line) : Exception(file, line) { }
103     InvalidParameterException(const char *message, const char *file, int line) : Exception(message, file, line) { }
104 
105     InvalidParameterException(const InvalidParameterException& e) : Exception(e) { }
106     InvalidParameterException &operator = (const InvalidParameterException &e)
107     {
108         Exception::operator =(e);
109         return *this;
110     }
111 };
112 
113 }
114 
115 #endif // EXCEPTION_H

【Exception.cpp】

 1 #include "Exception.h"
 2 #include <cstring>
 3 #include <cstdlib>
 4 
 5 namespace DTLib
 6 {
 7 
 8 void Exception::init(const char *message, const char *file, int line)
 9 {
10     /*
11      * message指向的字符串可能位于堆、栈或其他地方,如果直接用m_message = message,
12      * 会导致DTLib库无法控制字符串的生命周期,这是不安全的,因此使用strdup在库内部
13      * 单独拷贝保存一份。
14     */
15     m_message = (message ? strdup(message) : NULL);
16 17 if (file != NULL) 18 { 19 char sline[16] = {0}; 20 21 itoa(line, sline, 10); //将行号转换为字符串 22 23 /*动态申请内存,拷贝文件名,并拼接上行号,最终组成m_loction*/ 24 m_location = static_cast<char *>(malloc(strlen(file) + strlen(sline) + 2)); 25 m_location = strcpy(m_location, file); 26 m_location = strcat(m_location, ":"); 27 m_location = strcat(m_location, sline); 28 } 29 else 30 { 31 m_location = NULL; 32 } 33 } 34 35 Exception::Exception(const char *message) 36 { 37 init(message, NULL, 0); 38 } 39 40 Exception::Exception(const char *file, int line) 41 { 42 init(NULL, file, line); 43 } 44 45 Exception::Exception(const char *message, const char *file, int line) 46 { 47 init(message, file, line); 48 } 49 50 //从init()的实现可以看出,拷贝构造和赋值操作必须为深拷贝 51 Exception::Exception(const Exception &e) 52 { 53 m_message = strdup(e.m_message); 54 m_location = strdup(e.m_location); 55 } 56 57 Exception &Exception::operator = (const Exception &e) 58 { 59 if (this != &e) 60 { 61 free(m_message); 62 free(m_location); 63 64 m_message = strdup(e.m_message); 65 m_location = strdup(e.m_location); 66 } 67 68 return *this; 69 } 70 71 const char *Exception::message() const 72 { 73 return m_message; 74 } 75 76 const char *Exception::location() const 77 { 78 return m_location; 79 } 80 81 /* 82 * C++规定,只要自定义了析构函数,不管是不是纯虚函数,都必须提供实现, 83 * 否则其子类对象析构时,会因为找不到父类析构函数的实现而导致析构失败。 84 */ 85 Exception::~Exception() 86 { 87 free(m_message); 88 free(m_location); 89 } 90 91 }

 

注:本文整理于狄泰《数据结构开发实战教程》课程内容

posted @ 2018-05-20 14:10  原野追逐  阅读(426)  评论(0编辑  收藏  举报