赞
踩
18.1 C++内存高级话题-new、delete的进一步认识
18.2 C++内存高级话题-new内存分配细节探秘与重载类内operator new、delete
18.3 C++内存高级话题-内存池概念、代码实现和详细分析
18.4 C++内存高级话题-嵌入式指针概念及范例、内存池改进版
18.5 C++内存高级话题-重载全局new/delete、定位new及重载
之前学习了类中的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); } };
结果一目了然:类中的重载会覆盖掉全局的重载。
前面学习的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代码行时能够被调用。
其实可以重载很多版本的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里面要做什么事,完全由程序员来控制。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。