技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 编程语言 --> 通过引用计数解决野指针的问题(C&C++)

通过引用计数解决野指针的问题(C&C++)

浏览:3405次  出处信息

    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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
 
template 
class ptr_proxy
{
public:
    ptr_proxy(const T* pobj=NULL) : m_ppobj(NULL), m_pcount(NULL)
    {
        if (pobj == NULL)
        {
            return;
        }
        init(pobj);
    }
 
    ptr_proxy(const ptr_proxy& rhs) // 拷贝构造函数
    {
        m_ppobj = rhs.m_ppobj; // 指向同一块内存
        m_pcount = rhs.m_pcount; // 使用同一个计数值
        add_count();
    }
 
    virtual ~ptr_proxy()
    {
        dec_count();
    }
 
    /**
     * @brief   如果指向的对象被释放了,一定要调用这个函数让他知道
     */
    void set2null()
    {
        if (m_ppobj)
        {
            (*m_ppobj) = NULL;
        }
    }
 
    /**
     * @brief   copy构造函数
     *
     * @param   rhs         被拷贝对象
     *
     * @return  自己的引用
     */
    ptr_proxy& operator=(const ptr_proxy& rhs)
    {
        if( m_ppobj == rhs.m_ppobj ) // 首先判断是否本来就指向同一内存块
            return *this; // 是则直接返回
 
        dec_count();
 
        m_ppobj = rhs.m_ppobj; // 指向同一块内存
        m_pcount = rhs.m_pcount; // 使用同一个计数值
        add_count();
 
        return *this; // 是则直接返回
    }
 
    ptr_proxy& operator=(const T* pobj)
    {
        if(m_ppobj && *m_ppobj == pobj) // 首先判断是否本来就指向同一内存块
            return *this; // 是则直接返回
        dec_count();
 
        init(pobj);
 
        return *this;
    }
 
    /**
     * @brief   获取内部关联的obj的指针
     *
     * @return
     */
    T* true_ptr()
    {
        if (m_ppobj)
        {
            return *m_ppobj;
        }
        else
        {
            return NULL;
        }
    }
 
    /**
     * @brief   获取内部关联的obj的指针
     *
     * @return
     */
    T* operator*()
    {
        return true_ptr();
    }
 
    /**
     * @brief   获取内部关联的obj的个数
     *
     * @return  个数
     */
    int count()
    {
        if (m_pcount != NULL)
        {
            return *m_pcount;
        }
        return 0;
    }
 
    /**
     * @brief   判断智能指针是否为空
     *
     * @return
     */
    bool is_null()
    {
        if (m_ppobj == NULL || (*m_ppobj) == NULL)
        {
            return true;
        }
        return false;
    }
 
protected:
    void init(const T* pobj)
    {
        m_ppobj = new (T*)();
        *m_ppobj = (T*)pobj;
        m_pcount = new int(); // 初始化计数值为 1
        *m_pcount = 1;
    }
 
    void add_count()
    {
        if (m_pcount == NULL)
        {
            return;
        }
        (*m_pcount) ++;
    }
 
    /**
     * @brief   计数减1
     */
    void dec_count()
    {
        if (m_pcount == NULL || m_ppobj == NULL)
        {
            return;
        }
 
        (*m_pcount)--; // 计数值减 1 ,因为该指针不再指向原来内存块了
        if( *m_pcount <= 0 ) // 已经没有别的指针指向原来内存块了
        {
            //我们不去主动析构对象
            //free_sptr(*m_ppobj);//把对象析构
            if (m_ppobj != NULL)
            {
                delete m_ppobj;
                m_ppobj = NULL;
            }
 
            if (m_pcount != NULL)
            {
                delete m_pcount;
                m_pcount = NULL;
            }
        }
    }
 
protected:
    T** m_ppobj;
    int* m_pcount;
};
 
template 
class IPtrProxy
{
public:
    IPtrProxy() {
        m_ptr_proxy = (T*)this;
    }
    virtual ~IPtrProxy() {
        m_ptr_proxy.set2null();
    }
 
    ptr_proxy& get_ptr_proxy()
    {
        return m_ptr_proxy;
    }
 
protected:
    ptr_proxy m_ptr_proxy;
};
 
#endif

    我们来写段测试代码测试一下:

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#include \"ptr_proxy.h\"
 
using namespace std;
 
class A : public IPtrProxy
{
public:
    A() {}
    virtual ~A() {}
};
 
class B : public IPtrProxy
{
public:
    B() {}
    virtual ~B() {}
 
    void print()
    {
        printf(\"this is print\\\\n\");
    }
 
    void SetAPtr(const ptr_proxy& pptr)
    {
        m_ptr_a = pptr;
    }
 
    void check()
    {
        if (m_ptr_a.is_null())
        {
            printf(\"is null\\\\n\");
        }
        else
        {
            printf(\"is not null\\\\n\");
        }
    }
 
    ptr_proxy m_ptr_a;
};
 
int main(int argc, char **argv)
{
    A* a = new A();
    B* b = new B();
    b->SetAPtr(a->get_ptr_proxy());
    delete a;
    b->check();
 
    b->get_ptr_proxy().true_ptr()->print();
    delete b;
    return 0;
}

    输出为:

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

建议继续学习:

  1. Linus:为何对象引用计数必须是原子的    (阅读:11448)
  2. Linus:利用二级指针删除单向链表    (阅读:11335)
  3. C语言结构体里的成员数组和指针    (阅读:4819)
  4. 字符引用和空白字符    (阅读:3623)
  5. cpp智能指针的简单实现    (阅读:3073)
  6. C 语言中统一的函数指针    (阅读:3025)
  7. 变量引用可提供执行速度    (阅读:2891)
  8. PHP错误抑制符(@)导致引用传参失败的Bug    (阅读:2542)
  9. 重构发现:指针操作问题    (阅读:2389)
  10. 一个想当然造成的错误(函数引用参数的一个问题)    (阅读:2388)
QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1