首页 > 代码库 > 如何实现自己特定的内存管理,如何正确替换C++中的全局运算符new和delete
如何实现自己特定的内存管理,如何正确替换C++中的全局运算符new和delete
在谈下面的问题之前,请先看看写的这篇博客:new operator和operator new之间的区别
考虑下面的代码块,能运行,但是存在一些问题,我们将一一解答:
#include <stdio.h> #include <iostream> using namespace std; class A { public: A() { throw exception(""); } void operator delete(void *p, size_t s) { cout << "A::operator delete/n" << endl; } }; class B { public: virtual ~B(){} void operator delete(void* p,size_t n) throw() {cout << "operator delete B" << endl;} void operator delete[](void* p,size_t n) throw() {cout << "operator delete[] B" << endl;} void f(void* p,size_t n) throw() {cout << "function f" << endl;} }; class D:public B { public: void operator delete(void* p) throw() {cout << "operator delete D" << endl;} void operator delete[](void* p) throw() {cout << "operator delete[] D" << endl;} }; typedef void (B::*PMF)(void*,size_t); void main() { try { A *p = new A; // 抛出异常!调用A::operator delete() } catch (exception) { } D* pd1 = new D; delete pd1; B* pb1 = new D; delete pb1; D* pd2 = new D[10]; delete[] pd2; B* pb2 = new D[10]; delete[] pb2; PMF p1 = &B::f; //PMF p2 = &B::operator delete; }
运行结果如下:
A::operator delete/noperator delete Doperator delete Doperator delete[] Doperator delete[] D请按任意键继续. . .
注意:B的operator delete函数中还有第二个参数,而在D中却没有,这只是不同的编程风格问题,B中的这两个delete函数都是普通的内存释放函数,而不是placement delete(我们将在后面详细说明)。
①对于代码块B和D还是存在着一个隐藏的内存错误。在这两个类中都提供了operator delete()和operator delete[]()函数,但却没有提供相应的operator new()和operator new[]()。这是非常危险的,因为默认的operator new()和operator new[]()可能不会正确地实现我们所需要的功能。所以我们需要一对同时定义。
②函数operator new()和operator delete()的所有风格与静态函数的风格都是一致的,即使它们没有被声明为静态的。当我们在声明这些函数时,虽然C++并不会强制我们显式地使用“static”,但是我们通常应该显式的将函数operator new()和operator delete()声明为静态函数。它们永远都不能是非静态成员函数。
③对于下面的代码块
D* pd1 = new D;
delete pd1;
B* pb1 = new D;
delete pb1;
两者都是调用了D::operator delete(void*),这是因为B的析构函数被声明为虚函数,因此D的析构函数将被调用,这也就意味着:虽然B::operator delete()并没有被声明为虚函数(事实上,这是不可能的),但D::operator delete()也一定会被调用。为什么?
通常编译器在为每个析构函数生成代码时,会设置一个标志位,这个标志位的含义是“当对象被销毁后,是否应该销毁它?”(在销毁自动对象时,这个标志位是false,而在销毁动态对象时,这个标志位是true)。然后,在编译器生成的析构函数代码中,所做的最后一件事就是检查这个标志位,如果为true,则会去调用正确地operator delete()函数。这种技术就自动地确保了正确地行为,即虽然函数operator delete()是一个静态的函数并因此不能成为虚函数,但从行为来看却像是虚函数。
④对于下面的代码块
D* pd2 = new D[10];
delete[] pd2;
B* pb2 = new D[10];
delete[] pb2;
由运行结果可知两者都调用了D::operator delete[](void*),但是后者的行为时未定义的,C++规定:传递给函数operator delete[]的指针的静态类型与动态类型必须是相同的,因此我们永远都不要通过多态的方式来处理数组,应优先选择使用vector<>或者deque<>。
⑤对于下面的代码块
PMF p1 = &B::f;
//PMF p2 = &B::operator delete;
第一个赋值运算时合法的,就是简单的把一个成员函数的地址赋给了成员函数指针。
第二个赋值运算时非法的,因为函数void operator delete(void* p,size_t n) throw()并不是B的一个非静态函数,尽管它看上去像是一个非静态函数。
⑥由于A的构造函数会抛出异常,而且A定义了一个与operator new(size_t)
匹配的内存释放函数,因此编译器将调用那个内存释放函数然后抛出异常。
这里需要理解的是:若new
运算符在初始化对象时抛出异常并且存在与operator new
匹配的内存释放函数,那么这个内存释放函数将被调用以释放内存,异常在new
表达式的环境下继续传播。delete
运算符没有放置形式,但是存在放置形式的内存释放函数。非放置形式的内存释放函数与非放置形式的内存分配函数是匹配的;放置形式的内存释放函数与放置形式的内存分配函数,若它们的第二个及后续参数一致,则是匹配的。
⑦对于最开始所说的B中的两个delete函数都是普通的内存释放函数作详细说明:
正常的operator new签名式:
void* operator new(std::size_t) throw (std::bad_alloc);
对应的正常的operator delete签名式:
void operator delete(void* raw_memory)throw();//global 作用域中的正常签名式
void operator delete(void* raw_memory,std::size_t size)throw();//class作用域中典型的签名式
类似于new的placement版本,operator delete如果接受额外参数,便称为placement delete,例如:
struct Widget
{
static void* operator new(std::size_t size, std::ostream& log_stream)throw(std::bad_alloc);
static void operator delete(void* memory) throw();
static void operator delete(void* memory,std::ostream& log_stream)throw();
...
};
如果以下语句引发Widget构造函数抛出异常:
Widget* new_widget = new (std::cerr) Widget; //一如既往,但这次就不在发生泄漏。
然而如果没有抛出异常(大部分是这样的),客户代码中有个对应的delete,会发生什么事情:
delete pw; //call normal operator delete
调用的是正常形式的operator delete,而非其placement版本。请记住:placement delete只有在‘伴随placement
new调用而触发的构造函数‘出现异常时才会被调用。对着一个指针施行delete绝不会导致调用placement delete。
还有一点你需要注意的是:由于成员函数的名称会遮盖其外围作用域中的相同名称,你必须小心避免让class专属
news遮盖客户期望的其它news(包括正常版本)。默认情况下,C++在global作用域内提供以下形式的operator new:
void* operator new(std::size_t)throw(std::bad_alloc);//normal new.
void* operator new(std::size_t,void*)throw();//placement new
void* operator new(std::size_t, const std::nothrow_t&)throw();//nothrow new.see Item 49.
如果你在class内声明任何operator news,它会遮掩上述这些标准形式。除非你的意思就是要阻止class的客户使
用这些形式,否则请确保它们在你所生成的任何定制型operator new之外还可用。对于每一个可用的operator new也
请确定提供对应的operator delete。如果希望这些函数都有着平常的行为,只要令你的class专属版本调用global版
本即可。
为了完成以上所言的一个简单做法就是建立一个base class,内含所有正常形式的new和delete:
struct StandardNewDeleteForms
{
//normal new/delete
static void* operator new(std::size_t size)throw(std::bad_alloc)
{ return ::operator new( size ); }
static void operator delete(void* memory) throw()
{ ::operator delete( memory ); }
//placement new/delete
static void* operator new(std::size_t size,void* pointer)throw()
{ return ::operator new( size, pointer );}
static void operator delete(void* memory,void* pointer)throw()
{ return ::operator delete( memory, pointer ); }
//nothrow new/delete
static void* operator new(std::size_t size,const std::nothrow_t& no_throw)throw()
{ return ::operator new( size, no_throw ); }
static void operator delete(void* memory,const std::nothrow_t&)throw()
{ ::operator delete( memory ); }
};
凡是想自定形式扩充标准形式的客户,可利用继承机制及using声明式(Item 33)取得标准形式:
struct Widget:public StandardNewDeleteForms
{
using StandardNewDeleteForms::operator new;
using StandardNewDeleteForms::operator delete;
static void* operator new(std::size_t size, std::ostream& log_stream) throw(std::bad_alloc);
static void operator delete(void* memory,std::ostream& log_stream) throw();
...
};