条款8 写operator new 和operator delete 时要遵循常规
重写operator new时, 函数提供的行为要和系统缺省的operator new一致:
1)正确的返回值; 2)可用内存不够时调用出错处理函数; 3)处理0字节内存请求的情况; 避免隐藏标准形式的new;
1)如果内存分配请求成功, 返回指向内存的指针, 失败抛出std::bad_alloc异常;
operator new实际上不止一次尝试分配内存, 每次失败会调用出错处理函数(期望释放别处的内存), 只有在出错处理函数的指针为空的情况下才抛出异常.
Note 按C++标准要求, 在请求分配0字节的内存时, operator new也要返回一个合法指针.
非类成员形式的operator new伪代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
void *
operator new(size_t size) //
operator new 还可能有其它参数{ if (size
== 0) { //
处理0 字节请求时, size
= 1; //
把它当作1 个字节请求来处理 } while (1)
{ "分配
size 字节内存;" if (分配成功) return (指向内存的指针); //
分配不成功,找出当前出错处理函数 new_handler
globalHandler = set_new_handler(0); set_new_handler(globalHandler); if (globalHandler)
(*globalHandler)(); else throw std::bad_alloc(); }} |
>处理零字节的请求的技巧是把他作为请求一个字节来处理;
>把handler置为0然后再恢复是因为没法直接得到handler的指针, 必须调用set_new_handler;
>operator new包含一个无限循环: while(1), 跳出循环的条件是内存分配成功或出错处理函数完成事件中的一种:
得到可用内存; 安装了新的new-handler; 卸除了new-handler; 抛出bad_alloc类型的异常; 返回失败; 所以new-hander必须做到其中一件事, 否则循环无法结束;
operator new经常会被子类继承, 引出复杂度; 大多数指针对类所写的operator new只为特定的类设计的, 不是为其他类或子类设计的;
对于一个类X的operator new来说, 函数内部的行为在涉及到对象的大小时, 都是sizeof(X). 但由于存在继承, 基类中的operator new可能被调用给子类对象分配内存;
|
1
2
3
4
5
6
7
8
|
class Base
{ public: static void *
operator new(size_t size);...};class Derived: public Base //
Derived 类没有声明operator new{
... }; //Derived
*p = new Derived; //
调用Base::operator new |
>如果Base类的operator new不想处理这种情况, 简单的方法是把内存分配请求转给标准operator new来处理:
|
1
2
3
4
5
6
7
|
void *
Base::operator new(size_t size){ if (size
!= sizeof(Base)) //
如果数量“错误”,让标准operator new return ::operator new(size); //
去处理这个请求//... //
否则处理这个请求} |
>size != sizeof(Base)处理了size等于零的情况: C++标准规定独立的freestanding类的大小都是非零值; 即使Base没有成员, sizeof(Base)也总是非0; (非独立的类sizeof可能为零) [嵌套类??] size为零时, 请求就会转到::operator
new来处理;
如果想控制基于类的数组的内存分配, 必须实现operator new的数组形式: operator new[]("数组new");
写operator new[]时, 面对的是"原始"内存, 不能对数组里还不存在的对象进行操作; 还不知道数组对象的个数和大小;
基类的operator new[]会通过继承的方式被用来为子类对象的数组分配内存, 但子类对象一般比基类的大;
Base::operator new平[]里的每个对象大小不一定都是sizeof(Base), 数组对象的数量不一定就是 (请求字节数)/sizeof(Base);
对于operator delete和operator delete[], 要记住C++保证删除空指针总是安全的;
|
1
2
3
4
5
6
7
|
void operator delete(void *rawMemory){ if (rawMemory
== 0) return;
file://如果指针为空,返回 //释放
rawMemory指向的内存; return;} |
假设类的operator new将"错误"大小分配请求转给::operator new, 那么必须将"错误"大小的删除请求转给::operator delete;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class Base
{ //
和前面一样,只是这里声明了operator deletepublic: static void *
operator new(size_t size); static void operator delete(void *rawMemory, size_t size);...};void Base::operator delete(void *rawMemory, size_t size){ if (rawMemory
== 0) //
检查空指针 return; if (size
!= sizeof(Base)) { //
如果size"错误",让标准operator 来处理请求 ::operator delete(rawMemory); return; } "释放指向
rawMemory的内存;" return;} |
必须遵守operator new和operator delete的规定; 需要内存分配程序支持new-handler函数, 并正确处理零内存请求;
条款9 避免隐藏标准形式的new
内部范围声明的名称会隐藏掉外部范围的相同名称, 所以对于在类的内部和全局声明的同名函数f来说, 类成员函数会隐藏掉全局函数;
|
1
2
3
4
5
6
7
8
|
void f(); //
全局函数class X
{public: void f(); //
成员函数};X
x;f(); //
调用 fx.f(); //
调用 X::f |
>调用全局函数和成员函数时采用的是不同的语法形式;
但是如果在类里增加一个带多个参数的operator new函数:
|
1
2
3
4
5
6
7
8
9
10
|
class X
{public: void f();//
operator new 的参数指定一个 new-hander(new 的出错处理)函数 static void *
operator new(size_t size,
new_handler p);};void specialErrorHandler(); //
定义在别的地方X
*px1 = new (specialErrorHandler)
X; //
调用X::operator newX
*px2 = new X; //错误 |
>类里定义了"operator new"函数后, 会阻止对标准new的访问;
Solution 1)在类里写一个支持标准new调用方式的operator new, 和标准new做同样的事情;
|
1
2
3
4
5
6
7
8
9
|
class X
{public: void f(); static void *
operator new(size_t size,
new_handler p); static void *
operator new(size_t size)
{ return ::operator new(size);
}};X
*px1 = new (specialErrorHandler)
X; //
调用 X::operator new(size_t, new_handler)X*
px2 = new X; //
调用 X::operator new(size_t) |
>使用内联实现;
Solution 2)为每一增加到operator new的参数提供缺省值;
|
1
2
3
4
5
6
7
|
class X
{public: void f(); static void *
operator new(size_t size,
new_handler p = 0); //
p 缺省值为0};X
*px1 = new (specialErrorHandler)
X; //
正确X*
px2 = new X; //
也正确 |
>以后想对"标准"形式的new定制新的功能, 只需重写这函数;
本文详细介绍了C++中operator new和operator delete的使用规范,包括如何正确实现重写、处理零字节请求、避免隐藏标准new、实现operator delete[]和如何避免隐藏标准new的方法。此外,文章还讨论了如何处理内存分配和释放的异常情况,以及如何在类中定义自己的new和delete函数而不隐藏标准形式。

889

被折叠的 条评论
为什么被折叠?



