C++不像Java,C#语言,它没有垃圾回收机制,但是它提供了强大而灵活的管理机制,使得开发人员自己避免内存泄露。可以通过new 获得内存或创建对象,一定使用delete来释放,这样就避免内存泄露。同时也可以将分配和使用用类封装,从而保证没有内存泄露。
#include <iostream>
using namespace std;
#include <stdio.h>
#include <string.h>
class simpleClass
{
private:
char *m_buf;
size_t m_size;
public:
simpleClass(size_t n = 1)
{
m_buf = new char[n];
m_size = n;
}
~simpleClass()
{
printf("%d is deleted at %xd \n", m_size, m_buf);
delete[] m_buf;
}
char * GetBuf()
{
return m_buf;
}
};
void foo()
{
simpleClass a(10);
char *p = a.GetBuf();
strcpy(p, "Hello");
printf("%s\n", p);
}
int main()
{
foo();
printf("exit main()...\n");
return 0;
}
这个程序中,对char类型的内存分配封装在类simpleClass中,通过声明对象,并给所需内存的大小,调用GetBuf获取响应内存,这段内存在simpleClass的对象退出时会调用析构函数自动释放。但是还是存在不完美的地方,如果在foo()函数中增加一条赋值语句
void foo()
{
simpleClass a(10);
simpleClass b = a;
char *p = a.GetBuf(); // 增加的语句
strcpy(p, "Hello");
printf("%s\n", p);
}
在实现simpleClass时,并没有实现拷贝构造函数,因此编译器会构造一个默认的拷贝构造函数,执行位拷贝(bit copy)操作,即对象a的内容一个个字节的拷贝到对象b中,因此对象a中的m_buf和拷贝后对象b中的m_buf指向的是同一个地址的内存。当a和b都销毁时,m_buf中的内容被销毁两次。解决方案:
(1)禁止拷贝构造,将拷贝构造函数声明为私有的。
(2)使用引用计数,对使用内存维护一个计数器。当有指针指向这块内存,计数器加1,当指向这块内存的指针销毁时,计数器减1,只有当计数器减为0时,表示没有指针指向这块内存,这块内存才可以被释放。
#include <iostream>
using namespace std;
#include <stdio.h>
#include <string.h>
class simpleClass
{
private:
char *m_buf;
size_t m_size;
int *m_count;
public:
simpleClass(size_t n = 1)
{
m_buf = new char[n];
m_size = n;
m_count = new int; // 为m_count分配空间
*m_count = 1;
printf("m_count is:%d\n", *m_count);
}
simpleClass(const simpleClass& s)
{
m_size = s.m_size;
m_buf = s.m_buf;
m_count = s.m_count;
(*m_count)++;
printf("m_count is:%d\n", *m_count);
}
~simpleClass()
{
(*m_count)--;
printf("m_count is:%d\n", *m_count);
if (0 == *m_count)
{
printf("%d is deleted at %d\n", m_size, m_buf);
delete[] m_buf;
delete m_count;
}
}
char * GetBuf()
{
return m_buf;
}
};
void foo()
{
simpleClass a(10);
char *p = a.GetBuf();
strcpy(p, "Hello");
printf("%s\n", p);
simpleClass b = a;
printf("b=%s \n", b.GetBuf());
}
int main()
{
foo();
printf("exit main()...\n");
return 0;
}
分析:当a被构造时,m_count初始化为1,当执行b=a时,count增加为2。a 和 b指向同一块内存,存储的内容都是“hello”。当退出foo()时,b首先被销毁,m_count减1,但是m_count的值仍是大于0的,所以内存没有释放。当a销毁时,m_count的值减为0,才释放对应的内存。
如何在foo()函数里面添加两句代码
void foo()
{
simpleClass a(10);
char *p = a.GetBuf();
strcpy(p, "Hello");
printf("%s\n", p);
simpleClass b = a;
// 添加代码
simpleClass c(20);
c = a;
printf("b= %s,c= %s\n", b.GetBuf(), c.GetBuf());
}
在通过拷贝构造创建b之后,声明了一个c对象,申请的内存大小是20字节。然后a赋值给c,此时c指向了a的内存。而c原来指向的内存则无指针指向,因此被释放。但是程序没有处理,造成内存泄露。解决方案,使用运算符重载。
simpleClass& operator=(const simpleClass& s)
{
// 判断当前对象的 m_buf 是否和 s.m_buf 是否指向相同的地址
if (m_buf == s.m_buf)
return *this;
// 如果不是,当前对象引用计数减1
(*m_count)--;
// 如果引用计数为0,释放该对象指向的空间
if (*m_count == 0)
{
printf("%d is deleted at %xd\n", m_size, m_buf);
delete[] m_buf;
delete m_count;
}
// 当前对象指针 指向新的地址空间
m_buf = s.m_buf;
m_size = s.m_size;
m_count = s.m_count;
(*m_count)++;// 这也是为什么引用计数用指针的原因??
}
总算是没有错误了,但是这个智能指针还不完整,并没有实现真正指针那样的操作,如运算符*,->都没有重载。更多详细内容参考《C++ Primer》 400页。
推荐书籍:
《C++ Primer》(第5版)
*****************************分割线************************************
如果喜欢,帮忙点赞,推荐给好友
添加方式,在微信公众号
搜索:c语言每日一问
或者:Love_gcc123
或者长按二维码图片,就可以添加
原文地址:http://blog.csdn.net/u014304293/article/details/45092367