1.程序员明确的进行内存释放
对于c++程序员,最头脑的莫过于对动态分配的内存进行管理了。c++在堆上分配的内存,需要程序员负责对分配的内存进行释放。但有时内存的释放看起来并不件很轻松的事,如下程序
void func()
{
int *p = new int(0);
if(一些判断)
{
return;
}
p = new int(1);
delete p;
}
这个函数没有任何意义,只为说明问题。func函数至少有三处问题。1.一旦if的判断条件成立,就会立马执行返回语句。此时p所指向的内存将无法释放(这个新手一般都会犯的错误)。2. p = new int(1);语句,没有将原来分配在堆上的内存释放,造成原来那块内存永远不可能释放直到程序结束。3.这个问题并不是那么明显,假设func()函数的代码很长。
void func()
{
int *p = new int(0);
...
delete p;
}
在int *p = new int(0)和delete p之间有大量代码,而这中间的代码可能有代码已经执行过delete p,并且没有把p置0。此时p为悬挂指针。后面再执行delete p就会造成运行时错误。
2.一个类似于boost shared_ptr的智能指针管理类
下面是参照c++ primer及effective c++自己写的一个智能指针管理类。使用SmartPtr,就不再需要关心内存的释放问题了。
#ifndef SMARTPTR_INCLUDE_H
#define SMARTPTR_INCLUDE_H
#include <iostream>
using namespace std;
namespace commutil
{
template<typename T>
class SmartPtr
{
public:
SmartPtr(T *p = 0);
SmartPtr(const SmartPtr &);
SmartPtr &operator=(const SmartPtr &);
~SmartPtr();
T &operator *();
T *operator ->();
private:
void decreaseRef();
T *m_p;
int *m_useCnt;
};
template<typename T>
SmartPtr<T>::SmartPtr(T *p=0):m_p(p)
{
//调用此构造函数时,默认引用数为1
m_useCnt = new int(1);
}
template<typename T>
SmartPtr<T>::SmartPtr(const SmartPtr &rhs)
{
//使用复制构造函数,创建新的对象,引用数加1
this->m_p = rhs.m_p;
this->m_useCnt = rhs.m_useCnt;
(*m_useCnt)++;
}
template<typename T>
SmartPtr<T>::~SmartPtr()
{
decreaseRef();
}
template<typename T>
void SmartPtr<T>::decreaseRef()
{
if(--(*m_useCnt)==0)
{//当引用数为0时,释放heap内存
delete m_useCnt;
delete m_p;
}
}
template<typename T>
T &SmartPtr<T>::operator *()
{
//重载解引用符
return *m_p;
}
template<typename T>
T *SmartPtr<T>::operator ->()
{
//重载 ->运算符,返回真实的对象指针
return m_p;
}
template<typename T>
SmartPtr<T> &SmartPtr<T>::operator=(const SmartPtr &anotherPtr)
{
if(this==&anotherPtr)
{//防止自赋值
return *this;
}
//使用赋值运算符,原来所指对象的引用数减1。
decreaseRef();
m_p = anotherPtr.m_p;
m_useCnt = anotherPtr.m_useCnt;
++*m_useCnt;//指向新的对象,所指对象引用数加1
return *this;
}
}
#endif
3.使用SmartPtr的案例分析
3.1第一个SmartPtr的实例
void func()
{
SmartPtr<int> autoPtr(new int(1));
}
在func中,定义了SmartPtr的一个对象autoPtr,并用new int(1)在堆上分配一块内存,将分配的内存首地址传给SmartPtr的构造函数。此时autoPtr的m_useCnt的值为1。当func执行完成时,autoPtr对象超出其作用域,调用autoPtr的析构函数。在析构函数中调用私有的decreaseRef()函数,在decreaseRef()中将autoPtr的m_useCnt所内存值减1;此时m_useCnt所指内存值为0,执行delete m_useCnt;delete m_p;。至此在创建autoPtr对象时动态分配的内存被释放。
3.2带有return语句的SmartPtr
void func()
{
SmartPtr<int> autoPtr(new int(1));
if(判断条件)
{
return;
}
}
func()函数中的if判断条件成立时,执行return;语句后,autoPtr超出其作用范围,内存释放。分析见3.1
3.3 SmartPtr对象管理新的SmartPtr对象
SmartPtr<int> autoPtr1(new int(0));
SmartPtr<int> autoPtr2(new int(1));
autoPtr1 = autoPtr2;
执行autoPtr1 = autoPtr2,由于SmartPtr类重载了赋值运算符。实际执行的是SmartPtr的operator=()函数,在上面这个赋值语句中,先autoPtr1的m_useCnt所指内存值减1。此时m_useCnt所指内存值变为0,执行m_p所指内存释放。使用SmartPtr解决了1中所提的第二个问题。并且所有内存管理与释放工作都由SmartPtr进行,程序员不需要再关心什么时候进行内存释放。
3.4SmartPtr共享对象资源
与auto_ptr不同,SmartPtr使用引用计数机制保证多个SmartPtr对象可以管理同一个对象资源
void func()
{
SmartPtr<int> autoPtr1(new int(0));
SmartPtr<int> autoPtr2(autoPtr1);
}
执行SmartPtr autoPtr2(autoPtr1)调用SmartPtr的拷贝构造函数,在拷贝构造函数中将autoPtr2的m_useCnt及m_p分别指向autoPtr1所指向的内存,再将m_useCnt的引用数加1。func()函数结束时,两次调用SmartPtr的析构函数,两次析构函数后引用数为0,删除m_useCnt,m_p所指内存。
3.5SmartPtr访问类成员函数
class A
{
public:
void print()
{
cout<<"A"<<endl;
}
};
void func()
{
A *obj = new A();
obj->print();
delete obj;
}
SmartPtr重载了->运算符,可以将SmartPtr对象像指针一样访问成员函数。
void func()
{
SmartPtr<A> autoPtr(new A());
autoPtr->print();
}
4.小结
SmartPtr类实现了内存对象”自管理”,使用SmartPtr程序员不再需要关心动态内存的释放问题。与shared_ptr不同,SmartPtr类只能管理指针类型的资源,并且SmartPtr不支持自定义的资源释放函数。