C++使用new关键字创建的对象,被分配到堆内存空间,然后得到对象地址,当程序复杂庞大时容易发生访问地址bug或内存泄易做图ug。为了避免内存泄露并在调试程序时找到内存泄露的bug,可以重载new和delete函数,确保程序的内存安全。
new和delete关键字都可以作为operator来重载。重载的new函数规定一个参数size_t表示需要在堆空间上分配的内存大小,可以认为等于sizeof得到的大小,这个数值由系统创建对象时传递来。一般的实现方法就是:
#include <stdlib.h>
void* operator new(size_t type_size)
{
return malloc(type_size);
}
继承的delete函数规定一个参数void*表示要删除的对象地址,一般的实现方法就是:
[cpp] view plaincopy
#include <stdlib.h>
void operator delete(void* obj_ptr)
{
free(obj_ptr);
}
在这两个函数里加入一些调试代码,监视对象的创建和删除,就能很快找到bug。对于在函数堆栈上按值创建的对象则无需关心,因为函数在堆栈上创建和删除对象不会调用重载的new和delete方法。
对于修复特定的内存泄易做图ug,这样做已经足够解决问题,然而我们可以做的更完美一些。考虑一下COM和类似的接口系统,为了正确的引用接口,调用者必须放弃使用new和delete,通过接口的AddRef和Release实现内存安全。然而这样带来的负面影响很大,就是接口必须由实现者完整实现,一旦底层的接口实现被封装,接口类就无法当做普通类来继承,失去了面向对象编程的特性。而基于重载new和delete,我们可以实现一个既可以用new创建,delete删除,又可以用AddRef()引用,Release()释放的类,这个类的基本功能封装之后,可以当做普通的类被继承,而它和继承类都具有以下特性:
1. 创建者只关心用new来创建,用delete删除,就能保证对象内存安全。
2. 使用者只需要用AddRef引用它,用Release删除它,也能保证对象内存安全。
3. 这两种使用方式同时存在仍然可以保证内存安全。
这个类可以视为一种引用类型,所以命名为reference_type,通过重载new和delete,加上引用计数方法,最终得以实现。
#ifndef __REFERENCE_TYPE_HPP
#define __REFERENCE_TYPE_HPP
#include<exception>
#include<stdlib.h>
struct __declspec(novtable) reference_type
{
private:
__int64 __ref_count;
public:
reference_type(): __ref_count(1) { }
virtual ~reference_type()
{
this->__ref_count--;
}
void _hold()
{
this->__ref_count++;
}
void _drop()
{
this->__ref_count--;
if(this->__ref_count <= 0)
{
delete this;
}
}
void* operator new(size_t obj_size)
{
return malloc(obj_size);
}
void* operator new[](size_t arr_size) throw(std::bad_alloc)
{
throw std::bad_alloc();
}
void operator delete(void* obj_addr)
{
reference_type* obj_t = (reference_type*)obj_addr;
if(obj_t->__ref_count <= 0)
{
free(obj_addr);
}
}
void operator delete[](void* arr_addr) throw(std::bad_alloc)
{
throw std::bad_alloc();
}
};
#endif
这个对象可以被new和delete,同时也可以被引用和释放,_hold()相当于COM里的AddRef(),_drop()相当于COM里的Release()。实现的关键在于重载的new和delete也实现了引用计数功能。当delete函数使用了引用计数时,对象不是一定会被删除,而是直到引用计数归零时被删除,这样就实现了内存安全。最重要的是,这个类可以被继承了,保留面向对象编程的特性。
对象一旦具有安全引用的功能,就不能按值创建在函数堆栈上了,一旦函数退出自动清掉堆栈,引用就失效,再引用或释放对象就把程序搞崩溃。还有一个新的限制就是,不能创建对象数组!数组中的所有对象值是分配在一个连续的空间,只能一起创建并一起删除,此时引用计数同样全部失效。比如,引用数组某个成员后又用delete[]删除数组,其实被引用的成员也一起删除了,而程序还认为它处于引用状态并访问,最后同样把程序搞崩溃。代码中为了防止这个严重的情况发生,已经重载了new[]和delete[],并直接抛出异常。如果需要创建数组,需要实现一个引用安全的数组模板类,这已经跟C#和CLI等高级语言相似。
总的来说,对于reference_type和它的派生类,它们的优点就是使代码规范并让程序内存变安全,而以下列出一些缺点和使用限制,需要在应用中注意:
1. 不能在函数里创建对象值了,只能用new和delete;
2. 不能创建为对象数组,如果需要使用数组,只能创建为对象指针数组,或者实现一个引用安全的数组模板;
3. 确保_hold()和_drop()成对使用(当然new和delete也是永远需要成对使用),对象内存就可以安全;
4. 引用计数功能有极小的性能下降,但多数情况下可以忽略不计;
5. 这个方法仅限于C++面向对象编程,如果程序分配内存是直接使用C语言的malloc(),realloc(),free(),则需要其它方法,比如给malloc增加hook函数。