首页 > 代码库 > 如何实现自己特定的内存管理,如何正确替换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();
        ...
};