第十一课 异常类构建

  当代C++库基本上都会使用C++里面的异常特性,依赖于异常特性所创建的库的稳定性是非常好的。因此,我们创建的库也要引入异常类族。本节中,我们就给DTLib添加异常类族。

  异常的类型可以是自定义的类类型

  对于类类型的异常的匹配依旧是自上而下的严格匹配

  赋值兼容性原则在异常匹配中依然适用

  一般而言

    匹配子类异常的catch放在上部

    匹配父类异常的catch放在下部

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

顶层的父类是一个抽象类,不能定义对象,它是用来被继承的。通过继承的方式定义了5个异常类。分别是:

  计算异常

  越界异常

  内存不足异常

  参数错误异常

  空指针异常

一般而言这几种异常足够了。

具体如下:

计算异常:例如,1除以0的时候,可以抛出这个异常

空指针异常:如果我们在需要合法的指针时,得到的却是一个空指针,则可以抛出这种异常。

越界异常:访问数组时,有可能越界,越界的情况下就可以抛出这种异常

内存不足异常:动态的申请内存是,内存不足时,就抛出这种异常

参数异常错误:我们编写的算法肯定要接收参数,作为好的习惯,肯定要判断一下参数的合法性,不合法的参数,要抛出这个异常

异常类中的接口定义如下所示:

可以看到,析构函数是一个虚函数,而且是一个纯虚的析构函数。纯虚的析构函数仅仅用来说明当前的类是一个抽象类。其它没有任何更多的功能。

 异常类的定义如下:

#ifndef EXCEPTION_H
#define 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);

    Exception(const Exception& e);
    Exception& operator= (const Exception& e);

    virtual const char* message() const;
    virtual const char* location() const;

    virtual ~Exception() = 0;
};

class ArithmeticException : public Exception
{
public:
    ArithmeticException() : Exception(0, 0, 0) {}
    ArithmeticException(const char* message) : Exception(message) {}
    ArithmeticException(const char* file, int line) : Exception(file, line) {}
    ArithmeticException(const char* message, const char* file, int line) : Exception(message, file, line){}

    ArithmeticException(const ArithmeticException& e) : Exception(e) {}

    ArithmeticException& operator=(const ArithmeticException& e)
    {
        Exception::operator =(e);

        return *this;
    }
};

}

#endif // EXCEPTION_H

Exception.cpp文件如下:

 1 #include "Exception.h"
 2 #include <cstring>
 3 #include <cstdlib>
 4 
 5 using namespace std;
 6 
 7 namespace DTLib
 8 {
 9 
10 void Exception::init(const char *message, const char *file, int line)
11 {
12     m_message = strdup(message);
13 
14     if(file != NULL)
15     {
16         char sl[16] = {0};
17 
18         itoa(line, sl, 10);
19 
20         m_location = static_cast<char*>(malloc(strlen(file) + strlen(sl) + 2));
21         m_location = strcpy(m_location, file);
22         m_location = strcat(m_location, ":");
23         m_location = strcat(m_location, sl);
24     }
25     else
26     {
27         m_location = NULL;
28     }
29 }
30 
31 Exception::Exception(const char *message)
32 {
33     init(message, NULL, 0);
34 }
35 
36 Exception::Exception(const char *file, int line)
37 {
38     init(NULL, file, line);
39 }
40 
41 Exception::Exception(const char *message, const char *file, int line)
42 {
43     init(message, file, line);
44 }
45 
46 Exception::Exception(const Exception &e)
47 {
48     m_message = strdup(e.m_message);
49     m_location = strdup(e.m_location);
50 }
51 
52 Exception& Exception::operator =(const Exception& e)
53 {
54     if(this != &e)
55     {
56         free(m_message);
57         free(m_location);
58 
59         m_message = strdup(e.m_message);
60         m_message = strdup(e.m_location);
61     }
62 
63     return *this;
64 }
65 
66 const char* Exception::message() const
67 {
68     return m_message;
69 }
70 
71 const char* Exception::location() const
72 {
73     return m_location;
74 }
75 
76 Exception::~Exception()
77 {
78     free(m_message);
79     free(m_location);
80 }
81 
82 }

根据C++各种教程中的知识,纯虚函数不需要提供实现,纯虚函数等着子类来实现。但是在Exception.cpp中我们提供了纯虚函数(纯虚析构函数)的函数体,也就是给出了实现,这并不是一种错误的做法,这只是一种例外,因为这个纯虚函数是析构函数。C++规定,但凡我们定义了析构函数,不管析构函数是不是纯虚的,一定要提供实现,这是因为在析构一个对象的时候,最终会调用到父类的析构函数,如果父类的析构函数是纯虚的而且没有具体实现,那这一系列的析构是无法完成的。当调用到顶层父类的析构函数时,如果没有实现,这是一种很奇怪的现象。

  init函数中,第12行,我们使用了strdup函数将message的内容在堆空间中复制一份。因为,这个函数传入的message的内容可能是在栈上的,所以我们要复制一份。

第18行将整形数转换为字符串,第20行申请内存空间,最后的+2是因为,我们需要写入“:”以及最后的结束符。第21行是字符串复制,第22、23行是字符串拼接。

  Exception.h中的THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))是一个宏,其中e代表的是异常类的类型,m代表我们自定义的message信息。__FILE__由编译器解析,会将文件名以字符串的形式写到该处。__LINE__也是由编译器处理,会将行号以int型数据写到该处。

测试程序如下:

 1 #include <iostream>
 2 #include "Exception.h"
 3 using namespace std;
 4 using namespace DTLib;
 5 
 6 
 7 int main()
 8 {
 9     try
10     {
11         THROW_EXCEPTION(ArithmeticException, "test");
12     }
13     catch(Exception& e)
14     {
15         cout << "catch(Exception& e)" << endl;
16         cout << e.message() << endl;
17         cout << e.location() << endl;
18     }
19 
20     return 0;
21 }

执行结果如下:

 完善Exception.h中的其它异常类,如下所示:

  1 #ifndef EXCEPTION_H
  2 #define EXCEPTION_H
  3 
  4 namespace DTLib
  5 {
  6 
  7 #define THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))
  8 
  9 class Exception
 10 {
 11 protected:
 12     char *m_message;
 13     char *m_location;
 14 
 15     void init(const char* message, const char* file, int line);
 16 public:
 17     Exception(const char* message);
 18     Exception(const char* file, int line);
 19     Exception(const char* message, const char* file, int line);
 20 
 21     Exception(const Exception& e);
 22     Exception& operator= (const Exception& e);
 23 
 24     virtual const char* message() const;
 25     virtual const char* location() const;
 26 
 27     virtual ~Exception() = 0;
 28 };
 29 
 30 class ArithmeticException : public Exception
 31 {
 32 public:
 33     ArithmeticException() : Exception(0, 0, 0) {}
 34     ArithmeticException(const char* message) : Exception(message) {}
 35     ArithmeticException(const char* file, int line) : Exception(file, line) {}
 36     ArithmeticException(const char* message, const char* file, int line) : Exception(message, file, line){}
 37 
 38     ArithmeticException(const ArithmeticException& e) : Exception(e) {}
 39 
 40     ArithmeticException& operator=(const ArithmeticException& e)
 41     {
 42         Exception::operator =(e);
 43 
 44         return *this;
 45     }
 46 };
 47 
 48 class NullPointerException : public Exception
 49 {
 50 public:
 51     NullPointerException() : Exception(0, 0, 0) {}
 52     NullPointerException(const char* message) : Exception(message) {}
 53     NullPointerException(const char* file, int line) : Exception(file, line) {}
 54     NullPointerException(const char* message, const char* file, int line) : Exception(message, file, line){}
 55 
 56     NullPointerException(const NullPointerException& e) : Exception(e) {}
 57 
 58     NullPointerException& operator=(const NullPointerException& e)
 59     {
 60         Exception::operator =(e);
 61 
 62         return *this;
 63     }
 64 };
 65 
 66 class IndexOutOfBoundsException : public Exception
 67 {
 68 public:
 69     IndexOutOfBoundsException() : Exception(0, 0, 0) {}
 70     IndexOutOfBoundsException(const char* message) : Exception(message) {}
 71     IndexOutOfBoundsException(const char* file, int line) : Exception(file, line) {}
 72     IndexOutOfBoundsException(const char* message, const char* file, int line) : Exception(message, file, line){}
 73 
 74     IndexOutOfBoundsException(const IndexOutOfBoundsException& e) : Exception(e) {}
 75 
 76     IndexOutOfBoundsException& operator=(const IndexOutOfBoundsException& e)
 77     {
 78         Exception::operator =(e);
 79 
 80         return *this;
 81     }
 82 };
 83 
 84 class NoEnoughMemoryException : public Exception
 85 {
 86 public:
 87     NoEnoughMemoryException() : Exception(0, 0, 0) {}
 88     NoEnoughMemoryException(const char* message) : Exception(message) {}
 89     NoEnoughMemoryException(const char* file, int line) : Exception(file, line) {}
 90     NoEnoughMemoryException(const char* message, const char* file, int line) : Exception(message, file, line){}
 91 
 92     NoEnoughMemoryException(const NoEnoughMemoryException& e) : Exception(e) {}
 93 
 94     NoEnoughMemoryException& operator=(const NoEnoughMemoryException& e)
 95     {
 96         Exception::operator =(e);
 97 
 98         return *this;
 99     }
100 };
101 
102 class InvalidParameterException : public Exception
103 {
104 public:
105     InvalidParameterException() : Exception(0, 0, 0) {}
106     InvalidParameterException(const char* message) : Exception(message) {}
107     InvalidParameterException(const char* file, int line) : Exception(file, line) {}
108     InvalidParameterException(const char* message, const char* file, int line) : Exception(message, file, line){}
109 
110     InvalidParameterException(const InvalidParameterException& e) : Exception(e) {}
111 
112     InvalidParameterException& operator=(const InvalidParameterException& e)
113     {
114         Exception::operator =(e);
115 
116         return *this;
117     }
118 };
119 
120 }
121 
122 #endif // EXCEPTION_H

Exception.cpp文件和上面一样,再次贴出如下:

 1 #include "Exception.h"
 2 #include <cstring>
 3 #include <cstdlib>
 4 
 5 using namespace std;
 6 
 7 namespace DTLib
 8 {
 9 
10 void Exception::init(const char *message, const char *file, int line)
11 {
12     m_message = strdup(message);
13 
14     if(file != NULL)
15     {
16         char sl[16] = {0};
17 
18         itoa(line, sl, 10);
19 
20         m_location = static_cast<char*>(malloc(strlen(file) + strlen(sl) + 2));
21         m_location = strcpy(m_location, file);
22         m_location = strcat(m_location, ":");
23         m_location = strcat(m_location, sl);
24     }
25     else
26     {
27         m_location = NULL;
28     }
29 }
30 
31 Exception::Exception(const char *message)
32 {
33     init(message, NULL, 0);
34 }
35 
36 Exception::Exception(const char *file, int line)
37 {
38     init(NULL, file, line);
39 }
40 
41 Exception::Exception(const char *message, const char *file, int line)
42 {
43     init(message, file, line);
44 }
45 
46 Exception::Exception(const Exception &e)
47 {
48     m_message = strdup(e.m_message);
49     m_location = strdup(e.m_location);
50 }
51 
52 Exception& Exception::operator =(const Exception& e)
53 {
54     if(this != &e)
55     {
56         free(m_message);
57         free(m_location);
58 
59         m_message = strdup(e.m_message);
60         m_message = strdup(e.m_location);
61     }
62 
63     return *this;
64 }
65 
66 const char* Exception::message() const
67 {
68     return m_message;
69 }
70 
71 const char* Exception::location() const
72 {
73     return m_location;
74 }
75 
76 Exception::~Exception()
77 {
78     free(m_message);
79     free(m_location);
80 }
81 
82 }

主函数测试程序如下:

 1 #include <iostream>
 2 #include "Exception.h"
 3 using namespace std;
 4 using namespace DTLib;
 5 
 6 
 7 int main()
 8 {
 9     try
10     {
11         THROW_EXCEPTION(NoEnoughMemoryException, "test");
12     }
13     catch(NoEnoughMemoryException& e)
14     {
15         cout << "catch(NoEnoughMemoryException& e)" << endl;
16         cout << e.message() << endl;
17         cout << e.location() << endl;
18     }
19     catch(Exception& e)
20     {
21         cout << "catch(Exception& e)" << endl;
22         cout << e.message() << endl;
23         cout << e.location() << endl;
24     }
25 
26     return 0;
27 }

执行结果如下:

 由以上异常类族可以得出以下设计原则:

  如果要编写可复用的代码库,尽量使用面向对象技术进行架构,尽量使用异常机制分离正常逻辑和异常逻辑。

小结:

  现代C++库必然包含充要的异常类族

  所有库中的数据结构类都依赖于异常机制,依赖图如下

  异常机制能够分离库中代码的正常逻辑和异常逻辑

原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9499647.html