码迷,mamicode.com
首页 > 其他好文 > 详细

第3课 - 异常类构建

时间:2018-05-20 15:24:54      阅读:156      评论:0      收藏:0      [点我收藏+]

标签:class   析构函数   ret   操作符   c++   span   定义   ring   image   

本篇开始正式进入数据结构的相关内容,目标是基于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 = strdup(message);
 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 = strdup(message);
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 }

 

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

第3课 - 异常类构建

标签:class   析构函数   ret   操作符   c++   span   定义   ring   image   

原文地址:https://www.cnblogs.com/songhe364826110/p/9054031.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!