通过引用计数解决野指针的问题(C&C++)
C/C++代码中,野指针问题历来已久,当然,大家都知道new/delete要成对出现:
1 2 3 |
A *p = new A(); delete p; p = NULL; |
然而现实中却并不是总是如此简单,考虑如下例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class A { public: C() {} virtual ~C() {} }; class B { public: B() { m_pA = NULL; } virtual ~B() {} void SetA(A* p) { m_pA = p; } private: A* m_pA; }; A* pA = new A(); B* pB = new B(); pB->SetA(pA); delete pA; pA = NULL; //此时B中的m_pA已经无效,但是m_pA仍然不等于NULL,所以用 != NULL来判断不会有任何作用 |
简单来说,即pA被赋值为NULL,对B中的m_pA没有产生影响,那么怎么才能产生影响呢?
我们有两个做法:
第一种,在A的析构函数里面去B.SetA(NULL),但是这个相当于A去操作了B的数据,这是不合理的。而且当外面的指针非常多的时候,也根本不可能实现。
第二种方法呢?是的,我们可以用二级指针。
考虑如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class A { public: C() {} virtual ~C() {} }; class B { public: B() { m_ppA = NULL; } virtual ~B() {} void SetA(A** pp) { m_ppA = pp; } private: A** m_ppA; }; A** ppA = new (A*)(); (*ppA) = new A(); B* pB = new B(); pB->SetA(ppA); delete (*ppA); (*ppA) = NULL; //这个时候,B中的m_ppA也会收到影响,即*m_ppA == NULL |
这样确实可以解决野指针的问题,但是同时也引入了另一个问题,那就是ppA本身该什么时候释放呢?答案是:当最后一个引用ppA的类释放掉的时候。
最后一个,对,我们可以使用引用计数!
OK,正式放出我们的代码,其中使用了引用计数来确定当最后一个类释放掉的时候,ppA指针的内存被析构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
/*============================================================================= # # FileName: ptr_proxy.h # Desc: 这个类的作用,就是为了解决互指指针,不知道对方已经析构的问题 # # Author: dantezhu # Email: zny2008@gmail.com # HomePage: http://www.vimer.cn # # Created: 2011-06-13 15:24:12 # Version: 0.0.1 # History: # 0.0.1 | dantezhu | 2011-06-13 15:24:12 | initialization # =============================================================================*/ #ifndef __PTR_PROXY_H__ #define __PTR_PROXY_H__ #include |
我们来写段测试代码测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
#include |
输出为:
is null this is print
这个类最有效的使用场景是当出现大量互指指针时,那么指向对象的指针有效性判断就尤其重要,而这个类可以完美解决这个问题。
可能想的比较深的朋友会问,既然引用计数都已经用上了,那么为什么不直接通过引用计数来析构呢?
其实这几天我也在尝试,C++是否能引入完美的引用计数进行对象管理,而最终卡在一个地方,即:
如果,在类的构造函数里面,需要将引用计数对象构造出来,那么引用计数就会出现问题,如:
1 2 3 4 5 6 7 8 9 |
class A { public: A() { Count t(this); } virtual ~A() {} }; Count c = new A(); |
这个时候就会出现问题,除非把Count构造的计数对象放到一个对象池中管理,但是又会增加对象查找的成本,所以最终放弃了这个想法。
另外一点就是,C/C++的指针在很多情况下是最方便的,过度的封装很可能会弄巧成拙,所以适度就好。
OK,惯例代码还是放到googlecode上:
http://code.google.com/p/vimercode/source/browse/#svn%2Ftrunk%2Fptr_proxy
建议继续学习:
- Linus:为何对象引用计数必须是原子的 (阅读:11448)
- Linus:利用二级指针删除单向链表 (阅读:11335)
- C语言结构体里的成员数组和指针 (阅读:4819)
- 字符引用和空白字符 (阅读:3623)
- cpp智能指针的简单实现 (阅读:3073)
- C 语言中统一的函数指针 (阅读:3025)
- 变量引用可提供执行速度 (阅读:2891)
- PHP错误抑制符(@)导致引用传参失败的Bug (阅读:2542)
- 重构发现:指针操作问题 (阅读:2389)
- 一个想当然造成的错误(函数引用参数的一个问题) (阅读:2388)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:Dante 来源: Vimer
- 标签: 引用 指针
- 发布时间:2011-06-15 14:08:13
- [67] Go Reflect 性能
- [67] Oracle MTS模式下 进程地址与会话信
- [67] 如何拿下简短的域名
- [61] IOS安全–浅谈关于IOS加固的几种方法
- [60] 图书馆的世界纪录
- [59] 【社会化设计】自我(self)部分――欢迎区
- [58] android 开发入门
- [56] 视觉调整-设计师 vs. 逻辑
- [49] 给自己的字体课(一)——英文字体基础
- [47] 界面设计速成