您的位置:首页 > 编程语言 > C语言/C++

C++实现垃圾回收机制

2013-01-12 15:01 225 查看

C++实现垃圾回收机制

我想讨论的问题是,如何让下面的代码正确无误:

#include <iostream>

classHuman
{
public:
Human()
{
std::cout << "Human" << std::endl;
}
~Human()
{
std::cout << "~Human()" << std::endl;
}
};
int main()

{

Human*pHuman = new Human;

system(“pause”);

return0;

}

有没有发现其中的错误,而且这个错误很严重,嗯……我想你发现了,没有delete pHuman回收指针所指的内存空间,怎么知道?起码Human的析构函数没有调用,这会造成内存泄漏,非常严重的问题。就想你借了人家钱而不还一样,当然,如果你借的钱少,那问题不至于严重到不可收拾的地步,但是你借了很多钱而不还,那可麻烦了。

在Java、C#、AS这些语言中,上面的代码是没有任何问题的,因为你借了这些内存空间,在程序结束后,虚拟机会帮你还,也就是说,它们自带有垃圾回收机制,而C++?必须得手动delete,如果只是new出来一两个,手动delete还不觉得这么麻烦,但是如果你new出来很多东西,那怎么办,你得一个个记住,然后一个个delete?就像你借了很多人的钱,你必须记住谁谁借了钱给我,你这时会感叹,如果能有个人帮我还钱,那该多好啊,那样的话,你就只管借而不用担心钱没还而惹来的麻烦了。那在C++里,怎么实现呢?

你可能会想到用智能指针来管理,嗯……很好的想法,很自然的,你会修改上面的代码:

intmain()

{

std::auto_ptr<Human> pHuman(new Human);

system(“pause”);

return;

}

如果编译器说std里没有auto_ptr的话,#include<memory>就可以了,这就是智能指针的好处,只要你这个智能指针不是new创建出来的,它会答应帮你delete掉,如果你是new出来的,那么也得delete,否则它无法完成它的承诺。

也就是说要像下面那样:

int main()

{

std::auto_ptr<Human>*pHuman = new std::auto_ptr<Human>(new Human);

delete pHuman;

system(“pause”);

return;

}

如果你new创建智能指针的话,问题又继续循环了,所以智能指针不是用new出来的,否则你也必须delete。也就是说你借了那个帮你还钱的人的钱,所以你得先把钱还给他,他才帮你还清其他的钱。

OK,你保证你使用智能指针不new创建出来,那事情是不是就完了呢?当然还没,使用std::auto_ptr还有其他要注意的问题,想知道详细的话,研究Effective
C++和More Effective C++便可以了解。但在这里不是我讨论的重点,我想讨论的是,智能指针的原理是什么?看下面代码便清楚了:

classHumanPtr
{
private:
Human* m_ptr;
public:
HumanPtr(Human* ptr = 0):m_ptr(ptr)
{
}
~HumanPtr()
{
if(m_ptr)
{
deletem_ptr;
m_ptr = 0;
}
}
};
那么像下面那样子使用:

int main()

{

HumanPtrpHuman(new Human);

/.../

}

也就是只要把new创建出来的那个指针放到一个对象里保存,而这个对象不是使用new创建的,只要符合这个规则即可实现只需new创建,而不用delete。

那为什么会自动帮我delete呢?

我们知道,每个对象都有一个生命周期,当生命结束后,系统会调用它的析构函数,然后回收它的内存空间。像上面那个HumanPtr pHuman,它的生命周期是整个程序运行时间,如果程序结束,它也结束,因为它是在栈里开辟空间的,在栈里开辟的空间,系统会自动回收掉,而在堆里开辟的,系统无法自动做到,如果能做到,也就没必要在这里讨论这么多了,标准库也就没必要弄出一个什么智能指针的东西了。

因此,智能指针都应该是在栈里开辟空间。

当然std::auto_ptr比这个HumanPtr做的事情还要多,但是它便是基于这个思想的。但是,这样还是没有解决我的问题。

我想要的是下面的代码正确无误:

int main()

{

Human* pHuman = new Human;

system(“pause”);

return0;

}

现在的问题是,这段代码造成了内存泄漏,如何解决这个问题?

嗯……要解决这个问题,只需调用delete pHuman就可以了,那能不能让系统帮我们调用呢?答案是可以的,看下面的代码:

//.h头文件

//对象

classObject
{
public:
//自定义的operator new
static
void* operator new(std::size_t);
//自定义的operator delete
static
void operator delete(void* pVoid);

virtual~Object() = 0{};
};
//单体
classJSingleton
{
protected:
JSingleton(){};
virtual~JSingleton() = 0{};
private:
JSingleton(constJSingleton&);
JSingleton& operator=(const JSingleton&);
};
//工厂
classFactory:public JSingleton
{
protected:
Factory(){};
virtual~Factory();
private:
std::vector<Object*> m_pObjects;
friend
class auto_ptr<Factory>;
staticauto_ptr<Factory> s_factory;
public:
staticFactory* Instance();
//增加对象
voidAddObject(Object* pObject)
{
m_pObjects.push_back(pObject);
};
//移除对象
voidEraseObject(Object *pObject);
};
classHuman:public Object
{
public:
Human()
{
std::cout << "Human" << endl;
}
~Human()
{
std::cout << "~Human()" << endl;
}
};
//.cpp文件

#include<memory>

#include<iostream>

#include<vector>

auto_ptr<Factory>Factory::s_factory;
Factory*Factory::Instance()
{
//如果工厂没有创建
if(s_factory.get() == 0)
{
s_factory.reset(newFactory);
}
returns_factory.get();
}
Factory::~Factory()
{
//在工厂里保存的所有指针删除,回收空间
for (DWORD i= 0; i < m_pObjects.size(); ++i)
{
if(m_pObjects[i])
{
::deletem_pObjects[i];//调用全局的delete
}
}
}
//移除对象
voidFactory::EraseObject(Object *pObject)
{
vector<Object*>::iterator iter =m_pObjects.begin();
//遍历寻找对应的指针
while (iter!= m_pObjects.end())
{
if (*iter== pObject)
{
iter = m_pObjects.erase(iter);//移除操作返回的是移除后的下一个
::operatordelete(pObject);//调用全局的delete
return;
}
else
{
++iter;
}
}
}
void*Object::operatornew(std::size_tst)
{
Human *pHuman = (Human*)::operatornew(st);//调用全局的new
Factory *pf = Factory::Instance();//获得唯一实例化的引用
pf->AddObject(pHuman);//将创建后的指针放到工厂里保存
returnpHuman;
}
void Object::operatordelete(void* pObject)
{
Factory *pFactory = Factory::Instance();//获得唯一实例化的引用
pFactory->EraseObject((Object*)pObject);//移除工厂中保存对象的指针
}
//main主函数

intmain()

{

Human *pHuman = new Human;
system("pause");
return 0;
}

运行上面的代码,你会发现当程序结束后,Human的析构函数调用了,那Human的空间有没有回收掉呢?答案是肯定的,我测试过,是没问题的,你可以自己做一下测试,可以创建一万个Human出来,然后看一下内存,程序结束后,再看一下内存,需要注意的是程序结束后是看不到内存了,而内存的释放又是在程序结束后的,因此,你可以将工厂设置为可以delete掉的,可以提前结束它的生命,又或者是提供一个Clear函数接口来测试,那样就可以看到内存的变化了。这里的工厂用的是单体模式,保证只存在一份实例,如果对单体模式不了解的话,可以看下我的另一篇关于单体模式的文章。

当然,这里只是提供一个思路和一份实现而已,你可以有你自己的一份实现,但是原理都差不多,就是把new创建出来的指针放到一个对象管理,这里采用的是工厂,这个对象要保证如果用户不自行delete,那么在程序结束后,这个delete工作将由这个对象执行。如果Human是一个抽象类,上面那个Factory也能正常工作。

这里需要注意的是,在使用new和delete时的一些事情,当使用new实例化时,编译器做了两件工作:

(1)首先在堆内存开辟了该类大小的空间,也就是说会先调用operator new(std::size_t)函数开辟空间,至于调用全局的还是这个类的,就要看这个类是否重载了operator new(std::size_t),如果这个类重载了,便调用该类提供的,编译器会调用sizeof(Human)计算出这个类的内存大小,然后作为实参传入到这个函数中,这个函数分配好空间了,返回这个内存空间的首地址。

(2)分配好内存空间后,调用这个类的构造函数,上面调用的是默认构造函数。

那么当delete的时候,编译器也做了两件事,是new相反顺序的,

(1)首先调用这个对象的析构函数

(2)再调用void operatordelete(void*)函数进行回收空间,至于调用全局

还是这个类提供的,就看这个类是否重载了这个函数。

关于operator new
与operator delete的信息,可以研究一下C++ Primer第四版,那里会有更详细的说明。

还得注意的是,这里采用的是vector顺序表来存储指针,如果你担心内存,可以采用list链表才存储,因为vector的erase移除操作并非是真正的释放空间,虽然你delete掉了pHuman的指针所指的内存,但是vector内部并没有收回掉那4个字节的内存,因为它要预留着下次来使用,那样就不需要再分配内存了。当然,你可以调用vector的析构函数来释放vector保存指针的那4个字节内存。

还有个问题是,如果自定义一个新类型时,必须得继承Object,不然得自行提供operator new和operator delete,否则无法自动回收内存。

可能还有其他的情况我没考虑到,如果发现,强烈希望指出。希望这个技术能对你有用。

这是最后源代码的链接http://download.csdn.net/detail/zx504287/4988003
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: