18.5 C++内存高级话题-重载全局new/delete、定位new及重载

文章探讨了C++中的内存高级话题,包括如何重载全局和类内的operatornew和operatordelete,以及内存池的概念和实现。此外,还介绍了定位new(placementnew)的使用,它是如何在已分配的内存上初始化对象的,并展示了如何在类中重载定位new。
摘要由CSDN通过智能技术生成

18.1 C++内存高级话题-new、delete的进一步认识
18.2 C++内存高级话题-new内存分配细节探秘与重载类内operator new、delete
18.3 C++内存高级话题-内存池概念、代码实现和详细分析
18.4 C++内存高级话题-嵌入式指针概念及范例、内存池改进版
18.5 C++内存高级话题-重载全局new/delete、定位new及重载

5.重载全局new/delete、定位new及重载

  5.1 重载全局operator new和operator delete操作符

    之前学习了类中的operator new、operator delete以及operator new[]、operator delete[]的重载。忘记的读者请进行适当的复习。
    其实,也可以重载全局的operator new、operator delete以及operator new[]、operator delete[],当然,在重载这些全局函数的时候,一定要放在全局空间里,不要放在自定义的命名空间里,否则编译器会报语法错。
在文件的前面位置,增加如下代码:

void* operator new(size_t size) //重载全局operator new
{
	return malloc(size);
}
void* operator new[](size_t size)//重载全局operator new[]
{
	return malloc(size);
}
void operator delete(void* phead)//重载全局operator delete
{
	free(phead);
}
void operator delete[](void* phead)//重载全局operator delete[]
{
	free(phead);
}
class A
{
public:
	A() //构造函数
	{
		cout << "A::A()" << endl;
	}
	~A() //析构函数
	{
		cout << "A::~A()" << endl;
	}
}
int main()
{
	int* pint = new int(12);   //调用重载的operator new
	delete pint;               //调用重载的operator delete
	char* parr = new char[10]; //调用重载的operator new[]
	delete[] parr;             //调用重载的operator delete[]   	
	A* p = new A();            //调用重载的operator new ,之后也执行了类A的构造函数
	delete p;                  //执行了类A的析构函数,之后也调用了重载的operator delete
	A* pa = new A[3]();        //调用一次重载的operator new[],之后执行了三次类A的构造函数
	delete[] pa;               //执行了三次类A的析构函数,之后也调用了重载的operator delete[]  
}

在这里插入图片描述
    虽然可以重载全局的operator new、operator delete、operator new[]、operator delete[],但很少有人这样做,因为这种重载影响面太广。读者知道有这样一回事就行了。一般都是重载某个类中的operator new、operator delete,这样影响面比较小(只限制在某个类内),也更实用。
    当然,如果类A中又重载了operator new、operator delete、operator new[]、operator delete[],那么类中的重载会覆盖掉全局的重载。在类A中重载这几个操作符(成员函数),完整的类A代码如下:

class A
{
public:
	A() //构造函数
	{
		cout << "A::A()" << endl;
	}
	~A() //析构函数
	{
		cout << "A::~A()" << endl;
	}
	void* operator new(size_t size)
	{
		A* ppoint = (A*)malloc(size);
		return ppoint;
	}
	void  operator delete(void* phead)
	{
		free(phead);
	}
	void* operator new[](size_t size)
	{
		A* ppoint = (A*)malloc(size);
		return ppoint;
	}
		void operator delete[](void* phead)
	{
		free(phead);
	}
};

    结果一目了然:类中的重载会覆盖掉全局的重载。

  5.2 定位new(placementnew)

    前面学习的new操作符都是传统的new,调用关系如下,这个前面已经讲解过了:

A * pa = new A();
	operator new();
		malloc();
	A::A();

    除了传统new之外,还有一种new叫作“定位new”,翻译成英文就是placement new,因为它的用法比较独特,所以并没有对应的placementdelete的说法。
    那么,定位new和传统new有什么区别呢?当然有区别。在讲解区别之前,首先要注意定位new的层次关系。定位new也和传统new处于同一个层次,但定位new的功能却是:在已经分配的原始内存中初始化一个对象。请注意这句话的两个重要描述点:
  · 已经分配:意味着定位new并不分配内存,也就是使用定位new之前内存必须先分配好
  · 初始化一个对象,也就是初始化这个对象的内存,可以理解成其实就是调用对象的构造函数
    总而言之,定位new就是能够在一个预先分配好的内存地址中构造一个对象。
    存在一种可能性,在以后做的项目中,可能因为某些特殊的需要,读者可能会突然觉得定位new似乎比传统new灵活,更方便,这时就是定位new出场的时候。定位new的格式如下:

new (地址) 类类型(参数)

    这里直接通过一个范例来演示定位new。为了防止与重载的operator new、operator delete产生冲突等,把上面所写的代码全部注释掉。
    创建一个叫作PLA的类:

class PLA
{
public:
	int m_a;
	PLA() :m_a(0)   //构造函数
	{
		cout << "PLA::PLA()构造函数执行" << endl;
	}
	PLA(int tempvalue) :m_a(tempvalue) //构造函数
	{
		cout << "PLA::PLA(int tempvalue)构造函数执行" << endl;
	}
	~PLA() //析构函数
	{
		cout << "PLA::~PLA()析构函数执行" << endl;
	}
	//定位new操作符的重载,注意参数是比传统new多一个参数的
	void* operator new(size_t size, void* phead)
	{
		//这里增加一些自己的额外代码,用于统计之类的,但不要分配内存
		return phead;//收到内存开始地址也只返回内存开始地址即可
	}
};
int main()
{
	void* mymemPoint = (void*)new char[sizeof(PLA)]; //内存必须事先分配出来,为了内存分配通用性,这里返回void *类型
	//开始用这个返回的void*指针
	PLA* pmyAobj1 = new(mymemPoint) PLA();   //定位new:调用无参构造函数,这里并不额外分配内存
	void* mymemPoint2 = (void*)new char[sizeof(PLA)];
	PLA* pmyAobj2 = new(mymemPoint2) PLA(12); //定位new:调用带一个参数的构造函数,这里并不额外分配内存
	//释放
	pmyAobj1->~PLA(); //根据需要,有析构函数就可以调用析构函数
	pmyAobj2->~PLA();
	delete[](void*)pmyAobj1; //分配时用char[],释放时用delete[],本行等价于delete[](void*)mymemPoint;
	delete[](void*)pmyAobj2;//本行等价于delete[](void*)mymemPoint2;
}

在这里插入图片描述
    可以看到,一般来说,写程序的时候,构造函数都是不会被直接调用的(直接调用编译器会报错),而上面这种定位new的写法就等同于可以直接调用构造函数。
    而析构函数是能够直接调用的(上面的代码就直接调用了析构函数)。
    上面这些结论,希望读者有相关的认识。
    可以把断点设置在定位new这行代码上并跟踪调试,当程序执行流程停到断点行时切换到反汇编窗口观察定位new都调用了哪些代码,看到的内容如图所示。

在这里插入图片描述
    从图中可以看到定位new的调用关系如下表示:

PLA * pa = new (分配好的内存的首地址) PLA();//	定位new操作符
	operator new();	//函数 这里没有调用malloc
	PLA::PLA();		//调用构造函数

    图的operator new可以追踪进去,发现它并不像传统的new是要调用malloc来分配内存的,而这个operator new中看起来并没有分配内存。
    前面学习了针对一个传统的new操作符,可以在一个类中重载它所调用的operator new和operator delete函数,并在其中来分配和释放内存。
    其实,定位new所调用的operator new操作符也能重载,但是定位new没有对应的operator delete操作符。
    定位new所调用的operator new操作符的重载代码如下。在类PLA中,增加用public修饰的如下operator new成员函数,注意其形参:

定位new操作符的重载,注意参数是比传统new多一个参数的
void* operator new(size_t size, void* phead)
{
	//这里增加一些自己的额外代码,用于统计之类的,但不要分配内存
	return phead;//收到内存开始地址也只返回内存开始地址即可
}

    读者可以通过设置断点确认上面这段代码在执行定位new代码行时能够被调用。

  5.3 多种版本的operator new重载

    其实可以重载很多版本的operator new,只要每个版本参数不同就可以。第一个参数固定,类型都是size_t(类似于无符号整型),表示这个对象的sizeof值,其他参数通过调用new时指定进去即可。
    在main主函数中,代码如下:

	PLA* pla = new(1234, 56) PLA();   //这其实并没有实际分配内存,也没有调用类的构造函数

    编译一下,出现警告:“void *PLA::operator new(size_t,int,int)”表示未找到匹配的删除运算符。如果初始化引发异常,则不会释放内存。
    这个警告可以不理会,也可以在PLA类中增加对应的operator delete重载以避免这个警告(但这并不是必需的):

void* operator new(size_t size, int tvp1, int tvp2) 
{
	return NULL;
}
void  operator delete(void* phead, int tvp1, int tvp2)
{
	return;
}

    可以设置断点并进行跟踪调试,上面重载的operator new的第二个参数和第三个参数分别传递进去了1234和56,而第一个参数,系统默认传递进去的是sizoef(PLA)的值。
    另外注意,这种new的用法并不会去调用类PLA的构造函数。所以,这个重载的operator new里面要做什么事,完全由程序员来控制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值