当C++中类的成员为指针类型时,对该类的对象进行拷贝或者赋值时,多个对象副本的指针成员就会指向同一块动态申请的内存,如何来释放指针指向的内存将成为一个难以解决的问题:
class A
{
public:
A()
{
p = new int[5];
}
~A()
{
delete p;
}
void Release()
{
delete p;
}
private:
int count;
int* p;
}
int main()
{
A a1;
A a2(a1);
}
如果直接在析构中释放,当第二个对象被析构时,由于之前一个对象已经释放了内存,再delete就会出错,而如果在析构中不释放p指向的内存,将会导致内存泄露。如果由一个对象显式Release掉内存,另一个对象中的指针将成为“野指针”。因此需要一种策略来决定何时指针p被释放,解决的方法就是引用计数,引用计数的基本原则是所有的对象中看到的计数值必须是同步的,计数为0时说明没有其它对象正在使用这段内存了,该对象就可以用Release释放了。
如果直接把一个计数器count放到A中,显然是不行的,在上面的例子中,可以通过编码保证a1 a2中的count值是一致的,但如果再有一个对象由a2拷贝得到: A a3(a2) ,那么a1中的count值将无法被同步(C++ Primer中举的例子)
C++ Primer中给出的解决方案为,用一个单独的对使用者不可见的类ActualPointer来封装指针和引用计数,将暴露给使用者的类PointerWrapper声明为其友元类:
class ActualPointer
{
friend class PointerWrapper;
private:
int *p;
int count ;
public:
ActualPointer(int *value):p(value)
{
count = 1;
}
~ActualPointer()
{
delete p;
}
};
class PointerWrapper
{
private:
ActualPointer *ptr;
public:
PointerWrapper(int *value)
{
ptr = new ActualPointer(value);
}
~PointerWrapper()
{
ptr->count--;
if(ptr->count == 0)
delete ptr;
}
PointerWrapper(const PointerWrapper &pw):ptr(pw.ptr)
{
pw.ptr->count++;
}
};
int main()
{
int* value = new int[5];
PointerWrapper pw(value);
PointerWrapper pw2(pw);
return 0;
}
可以看出,当对PointerWrapper类的对象进行拷贝时,由于多个拷贝中使用的是同一个ActualPointer对象,可以保证count的值是一致的,而当一个PointerWrapper对象被析构时,会判断其指向的ActualPointer中的计数值,为0时才释放实际的内存,如果不为0则不会导致实际的指针被释放,使用时也不用显示释放内存,当PointerWrapper析构时会自动进行判断并释放。
这种方法不错,但我个人觉得还是挺难以理解的,其实还有另一种更简单的方法只需要在最初的版本上稍加改动即可,之前将count值直接放入对象中不可行的原因是各个对象中的count值无法同步,因此只需要将count的类型修改为int * 即可,每次拷贝时各个副本中count指针指向的是同一个int,就可以保证计数器的值是同步的了。
class SmartPointer2
{
private:
int *value;
int* count;
public:
SmartPointer2(int *v):value(v),count(new int)
{
*count = 1;
}
SmartPointer2(const SmartPointer2 &sp):value(sp.value),count(sp.count)
{
++*count;
}
~SmartPointer2()
{
--*count;
if(*count == 0)
{
delete value;
delete count;
}
}
};
int main()
{
int* value = new int[5];
SmartPointer2 sp(value);
SmartPointer2 sp2(sp);
return 0;
}