第二十六课 典型问题分析(Bugfix)

问题1:

glibc中的strdup实现如下:

没有对参数s进行空指针判断。

我们的Exception.cpp中应做改进:

在第12行进行判断空指针操作。

 问题2:

t1在析构时会抛出异常,我们在remove一个对象时,要保证链表还是合法的,也就是保证异常安全性。

如上图,我们期望打印的链表长度为2。

示例程序:

 1 #include <iostream>
 2 #include "LinkList.h"
 3 
 4 using namespace std;
 5 using namespace DTLib;
 6 
 7 class Test : public Object
 8 {
 9     int m_id;
10 public:
11     Test(int id = 0)
12     {
13         m_id = id;
14     }
15 
16     ~Test()
17     {
18         if( m_id == 1 )
19         {
20             throw m_id;
21         }
22     }
23 };
24 
25 int main()
26 {
27     LinkList<Test> list;
28     Test t0(0), t1(1), t2(2);
29 
30     try
31     {
32         list.insert(t0);
33         list.insert(t1);
34         list.insert(t2);
35 
36         list.remove(1);
37     }
38     catch(int e)
39     {
40         cout << e << endl;
41         cout << list.length() << endl;
42     }
43 
44     return 0;
45 }

结果如下:

程序直接崩溃了。

vc下的运行结果如下:

打印e的值为1,链表长度为3,意味着单链表的状态出问题了。

我们的remove函数没有考虑异常安全性。

修改LinkList.h:

  1 #ifndef LINKLIST_H
  2 #define LINKLIST_H
  3 
  4 #include "List.h"
  5 #include "Exception.h"
  6 
  7 namespace DTLib
  8 {
  9 
 10 template < typename T >
 11 class LinkList : public List<T>
 12 {
 13 protected:
 14     struct Node : public Object
 15     {
 16         T value;
 17         Node* next;
 18     };
 19 
 20     mutable struct : public Object
 21     {
 22         char reserved[sizeof(T)];
 23         Node* next;
 24     }m_header;
 25 
 26     int m_length;
 27     int m_step;
 28     Node* m_current;
 29 
 30     Node* position(int i) const    //  O(n)
 31     {
 32         Node* ret = reinterpret_cast<Node*>(&m_header);
 33 
 34         for(int p = 0; p < i; p++)
 35         {
 36             ret = ret->next;
 37         }
 38 
 39         return ret;
 40     }
 41 
 42     virtual Node* create()
 43     {
 44         return new Node();
 45     }
 46 
 47     virtual void destroy(Node* pn)
 48     {
 49         delete pn;
 50     }
 51 
 52 public:
 53     LinkList()
 54     {
 55         m_header.next = NULL;
 56         m_length = 0;
 57         m_step = 1;
 58         m_current = NULL;
 59     }
 60 
 61     bool insert(const T& e)
 62     {
 63         return insert(m_length, e);
 64     }
 65 
 66     bool insert(int i, const T& e)   // O(n)
 67     {
 68         bool ret = ((0 <= i) && (i <= m_length));
 69 
 70         if( ret )
 71         {
 72             Node* node = create();
 73 
 74             if( node != NULL )
 75             {
 76                 Node* current = position(i);
 77 
 78                 node->value = e;
 79                 node->next = current->next;
 80                 current->next = node;
 81 
 82                 m_length++;
 83             }
 84             else
 85             {
 86                 THROW_EXCEPTION(NoEnoughMemoryException, "No memery to insert new element...");
 87             }
 88         }
 89 
 90         return ret;
 91     }
 92 
 93     bool remove(int i)   // O(n)
 94     {
 95         bool ret = ((0 <= i) && (i < m_length));
 96 
 97         if( ret )
 98         {
 99             Node* current = position(i);
100 
101             Node* toDel = current->next;
102 
103             current->next = toDel->next;
104 
105             m_length--;
106 
107             destroy(toDel);
108 
109         }
110 
111         return ret;
112     }
113 
114     bool set(int i, const T& e)   //  O(n)
115     {
116         bool ret = ((0 <= i) && (i < m_length));
117 
118         if( ret )
119         {
120             position(i)->next->value = e;
121         }
122 
123         return ret;
124     }
125 
126     T get(int i) const
127     {
128         T ret;
129 
130         if( get(i, ret) )
131         {
132             return ret;
133         }
134         else
135         {
136             THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
137         }
138 
139         return ret;
140     }
141 
142     bool get(int i, T& e) const    // O(n)
143     {
144         bool ret = ((0 <= i) && (i < m_length));
145 
146         if( ret )
147         {
148             e = position(i)->next->value;
149         }
150 
151         return ret;
152     }
153 
154     int find(const T& e) const    //  O(n)
155     {
156         int ret = -1;
157         int i = 0;
158 
159         Node* node = m_header.next;
160 
161         while( node )
162         {
163             if( node->value == e )
164             {
165                 ret = i;
166                 break;
167             }
168             else
169             {
170                 node = node->next;
171                 i++;
172             }
173         }
174 
175         return ret;
176     }
177 
178     int length() const   // O(1)
179     {
180         return m_length;
181     }
182 
183     void clear()    //  O(n)
184     {
185         while( m_header.next )
186         {
187             Node* toDel = m_header.next;
188 
189             m_header.next = toDel->next;
190 
191             m_length--;
192 
193             destroy(toDel);
194         }
195 
196         // m_length = 0;
197     }
198 
199     bool move(int i, int step = 1)
200     {
201         bool ret = (0 <= i) && (i < m_length) && (step > 0);
202 
203         if( ret )
204         {
205             m_current = position(i)->next;
206             m_step = step;
207         }
208 
209         return ret;
210     }
211 
212     bool end()
213     {
214         return (m_current == NULL);
215     }
216 
217     T current()
218     {
219         if( !end() )
220         {
221             return m_current->value;
222         }
223         else
224         {
225             THROW_EXCEPTION(InvalidOperationException, "No value at current position ...");
226         }
227     }
228 
229     bool next()   //每次移动step步
230     {
231         int i = 0;
232 
233         while((i < m_step) && !end())
234         {
235             m_current = m_current->next;
236             i++;
237         }
238 
239         return (i == m_step);
240     }
241 
242     ~LinkList()   //  O(n)
243     {
244         clear();
245     }
246 };
247 
248 }
249 
250 #endif // LINKLIST_H

在remove函数中,先让m_length--,再做摧毁节点的操作。

在clear函数中,注释掉原来的196行,添加第191行,每次摧毁前让m_length--。

问题3:

测试程序如下:

结果如下:

删除之后,游标m_current指向这个释放之后的内存,current函数返回的是这个释放了的内存中保存的value。

 图解:

删除之后current函数返回的这块被释放了的堆内存中的value的值,所以会打印出随机值。

修改LinkList.h中的remove函数:

 1 bool remove(int i)   // O(n)
 2     {
 3         bool ret = ((0 <= i) && (i < m_length));
 4 
 5         if( ret )
 6         {
 7             Node* current = position(i);
 8 
 9             Node* toDel = current->next;
10 
11             if( m_current == toDel )
12             {
13                 m_current = toDel->next;
14             }
15 
16             current->next = toDel->next;
17 
18             m_length--;
19 
20             destroy(toDel);
21 
22         }
23 
24         return ret;
25     }

我们添加了第11-14行,在删除时,先进行判断,如果m_current和toDel指向的是同一个节点,那么先将m_current指向toDel的下一个节点,然后再删除。

运行结果如下:

 问题4:

 程序改进:

 1 void destroy(Node* pn)
 2     {
 3         SNode* space = reinterpret_cast<SNode*>(m_space);
 4         SNode* psn = dynamic_cast<SNode*>(pn);
 5 
 6         for(int i = 0; i<N; i++)
 7         {
 8             if( psn == (space + i))
 9             {
10                 m_used[i] = 0;
11                 psn->~SNode();
12                 break;
13             }
14         }
15     }

添加了第12行的break。

问题5:

StaticLinkList的构造函数中没有申请系统资源,从资源方面来看不用提供析构函数。

不提供析构函数的第二个条件是,该类为一个独立的类,没有任何继承关系,但是我们StaticLinkList不满足。

StaticLinkList继承自LinkList,LinkList中有析构函数,且这个析构函数调用了一个虚函数,但是多态是不会发生的。

构造函数和析构函数中不会发生多态。

StaticLinkList类中没有clear函数,因此,默认的析构函数会调用到LinkList的析构函数,进一步调用到clear函数,这个clear函数在子类和在父类中调用是一样的。

如果子类StaticLinkList中也有一个clear函数版本,那就要提供析构函数了。因为这两个含本的析构函数都要调用到。

但是我们再仔细观察可以发现,在clear函数中还调用了destroy函数,而这个函数在子类和父类中各有一个版本。

子类中的版本是:

父类中提供的版本是:

 这意味着父类中的析构函数被调用的时候,调用到的destroy一直是父类中的destroy。子类中的destroy无法被调用到,因为析构函数中不能发生多态。

析构时父类中的destroy直接delete掉申请的空间,而这个空间是在我们的预留区域中申请的,不是堆空间,因此,可能会出现bug,造成程序的不稳定,因为delete关键字只能释放堆空间。

改进程序,添加子类的析构函数:

 1 #ifndef STATICLINKLIST_H
 2 #define STATICLINKLIST_H
 3 
 4 #include "LinkList.h"
 5 
 6 namespace DTLib
 7 {
 8 
 9 template< typename T, int N >
10 class StaticLinkList : public LinkList<T>
11 {
12 protected:
13     // Node和泛指类型T有关系,因此,不能直接在子类中使用sizeof(Node),而应该
14     // sizeof(LinkList<T>::Node)
15     // unsigned char m_space[sizeof(typename LinkList<T>::Node) * N];  // 预留空间
16     typedef typename LinkList<T>::Node Node;
17 
18     struct SNode : public Node
19     {
20         void* operator new(unsigned int size, void* loc)
21         {
22             (void)size; // 消除 size没有使用的警告
23             return loc;
24         }
25     };
26 
27     unsigned char m_space[sizeof(SNode) * N];  // 预留空间
28     int m_used[N];   //预留空间的标记数组
29 
30     Node* create()
31     {
32         SNode* ret = NULL;
33 
34         for(int i = 0; i < N; i++)
35         {
36             if( !m_used[i] )
37             {
38                 ret = reinterpret_cast<SNode*>(m_space) + i;
39                 ret = new(ret)SNode(); //在指定空间ret上调用SNode类的构造函数。
40                 m_used[i] = 1;
41                 break;
42             }
43         }
44 
45         return ret;
46     }
47 
48     void destroy(Node* pn)
49     {
50         SNode* space = reinterpret_cast<SNode*>(m_space);
51         SNode* psn = dynamic_cast<SNode*>(pn);
52 
53         for(int i = 0; i<N; i++)
54         {
55             if( psn == (space + i))
56             {
57                 m_used[i] = 0;
58                 psn->~SNode();
59                 break;
60             }
61         }
62     }
63 
64 public:
65     StaticLinkList()
66     {
67         for(int i = 0; i < N; i++)
68         {
69             m_used[i] = 0;
70         }
71     }
72 
73     int capacity()
74     {
75         return N;
76     }
77 
78     ~StaticLinkList()
79     {
80         this->clear();
81     }
82 };
83 
84 }
85 
86 #endif // STATICLINKLIST_H

添加了第78-81行的析构函数,这时的clear是从父类继承来的,但是clear中的destroy函数就是子类自己实现的了。

这样的话析构时,先调用子类的析构函数,然后是子类继承过来的clear函数,然后子类的destroy函数。子类的析构函数执行完时,再调用父类的析构函数,然后父类的clear函数,最后调用父类的destroy函数。但是单步执行时,我们发现并没有调用到父类的destroy函数,这里可能令人迷惑,其实是因为调用父类的析构函数时,clear函数中的while循环已经不成立的,也就不会再调用父类的destroy函数了。单步执行时要注意这一点,调用不到父类的destroy是因为条件不成立,而不是其他的机制导致的。如下:

从子类的析构函数执行完,到调用到父类的析构函数时,clear函数中的190行的条件已经不成立了。

我们在子类的析构函数和destroy函数,父类的析构函数和destroy函数加上打印,测试程序如下:

 1 #include <iostream>
 2 #include "StaticLinkList.h"
 3 
 4 using namespace std;
 5 using namespace DTLib;
 6 
 7 
 8 int main()
 9 {
10     StaticLinkList<int, 10> list;
11 
12     list.insert(0);
13 
14     cout << list.get(0) << endl;
15 
16     return 0;
17 }

结果如下:

问题6:

没有必要定义多维数组的类,由一位数组就可以构成。

示例程序:

 1 #include <iostream>
 2 #include "StaticLinkList.h"
 3 #include "DynamicArray.h"
 4 
 5 using namespace std;
 6 using namespace DTLib;
 7 
 8 
 9 int main()
10 {
11     StaticLinkList<int, 10> list;
12 
13     DynamicArray< DynamicArray<int> > d;
14 
15     d.resize(3);
16 
17     for(int i=0; i < d.length(); i++)
18     {
19         d[i].resize(3);
20     }
21 
22     for(int i=0; i<d.length(); i++)
23     {
24         for(int j=0; j<d[i].length(); j++)
25         {
26             d[i][j] = i * j;
27         }
28     }
29 
30     for(int i=0; i<d.length(); i++)
31     {
32         for(int j=0; j<d[i].length(); j++)
33         {
34             cout << d[i][j] << " ";
35         }
36 
37         cout << endl;
38     }
39 
40     return 0;
41 }

结果如下:

还可以构造不规则的数组:

 1 #include <iostream>
 2 #include "StaticLinkList.h"
 3 #include "DynamicArray.h"
 4 
 5 using namespace std;
 6 using namespace DTLib;
 7 
 8 
 9 int main()
10 {
11     StaticLinkList<int, 10> list;
12 
13     DynamicArray< DynamicArray<int> > d;
14 
15     d.resize(3);
16 
17     for(int i=0; i < d.length(); i++)
18     {
19         d[i].resize(i + 1);
20     }
21 
22     for(int i=0; i<d.length(); i++)
23     {
24         for(int j=0; j<d[i].length(); j++)
25         {
26             d[i][j] = i + j;
27         }
28     }
29 
30     for(int i=0; i<d.length(); i++)
31     {
32         for(int j=0; j<d[i].length(); j++)
33         {
34             cout << d[i][j] << " ";
35         }
36 
37         cout << endl;
38     }
39 
40     return 0;
41 }

第19行使得每个数组元素大小不一样,运行结果如下:

实践经验:

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