在C++中,忘记释放指针指向的内存,是造成内存泄露的一个主要原因。如果我们能保证,对每一个int* p = new int; 能有一个 delete p; 匹配,就能很好的管理内存。构造函数和析构函数是成对出现的好伙伴,把指针操作包装成一个类。重载运算符*,这样在语法上可以像使用普通指针一样使用My_IntPtr。
class My_IntPtr
{
public:
My_IntPtr(int* p)
{
m_p = p;
}
~My_IntPtr()
{
delete m_p;
}
int& operator*()
{
return *m_p;
}
private:
int* m_p;
};
通过My_IntPtr来使用指针,不会担心内存泄露的问题。
int* p = new int;
My_IntPtr pi(p);
*(pi.m_p) = 10; //访问成员变量方法(成员变量要public的才行)
*pi = 18; //利用了重载的*运算符
P.S. 运算符重载:只是一种语法上的方便,本质上它就是函数调用,函数的名字是operator@,其中@是被重载的运算符。
返回值int&,而不是int。如果返回int型的话,相当于是个常量,不能作为左值(可以改变值的变量)被赋值了,这与平常的习惯不符。
以下三种情况,编译器报错:left operand must be l-value
*pi = 18; //利用了重载的*运算符,且返回int
const int num = 5;
num = 9;
int a[3];
a++;
返回 int& 的值:
My_IntPtr pi(new int);
*pi = 18; //重载*,返回int&
int& i = *pi;
i = 19;
cout << *pi << endl; //Output: 19
如果类里面有指针指向动态分配的内存,那么要为这个类添加一个拷贝构造函数和赋值运算符,不然按照默认的浅拷贝,一旦做了赋值,两个对象的指针会指向同一块内存,析构的时候会出错。
可见 pi和pi_2的地址是一样的,指向同一块内存。
析构时,在C:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\dbgdel.cpp 的delete() 中报错。
深拷贝不适合,如果智能指针指向一个复杂的对象呢,如果这个对象有一个私有的拷贝构造函数不能拷贝呢。
当当当,我们采用引用计数的方法来实现智能指针。为每个指针维护一个引用计数值,每次拷贝构造或者赋值,计数+1,因为指向该内存块的指针多了一个;每当指针销毁,计数值-1,当计数值=0时,没有指针指向该内存块,可以安全的释放了。
class My_IntPtr
{
public:
My_IntPtr(int* p)
{
m_p = p;
m_count = new int;
*m_count = 1; //初始化计数为1
}
//拷贝构造函数
My_IntPtr(const My_IntPtr& rhs)
{
m_p = rhs.m_p; //指向同一块内存
m_count = rhs.m_count; //使用同一个计数值
(*m_count)++; //计数值加1
}
~My_IntPtr()
{
(*m_count)--;
if (m_count == 0) //计数值为0,释放内存
{
delete m_p;
delete m_count;
}
}
//赋值运算符
My_IntPtr& operator=(const My_IntPtr& rhs)
{
if (m_p == rhs.m_p) //首先判断是否本来就指向同一块内存
return *this;
(*m_count)--; //计数值减1,因为该指针已经不指向原来的内存块了
if (m_count == 0) //计数值为0,释放内存
{
delete m_p;
delete m_count;
}
m_p = rhs.m_p;
m_count = rhs.m_count;
(*m_count)++;
}
int& operator*()
{
return *m_p;
}
private:
int* m_p;
int* m_count;
};
注意赋值运算符里的处理,要先释放原来指向的内存,再指向新的内存。
这是一个用C++实现的简单的垃圾收集机制。
目前C++中有一个引用计数的智能指针,boost::shared_ptr。下一篇文章来讨论。
【参考】