<条款5>对应的new和delete要采用相同的形式。
这一条没什么可说的,注意new的是不是数组就可以了。其中有两点需要注意:
1.在写一个包含指针数据成员,并且提供多个构造函数的类时,构造中new的形势要相同,否则析构中的delete便"为难"了。
2.喜欢用typedef的人需要注意,最好杜绝对数组类型用typedefs。
=====================================
<条款6>析构函数里对指针成员调用delete
增加一个指针成员意味着几乎都要进行下面的工作:
- 在每个构造函数里对指针进行初始化。对于一些构造函数,如果没有内存要分配给指针的话,指针要被初始化为0(即空指针)。
- 删除现有的内存,通过赋值操作符分配给指针新的内存。
- 在析构函数里删除指针。
=====================================
<条款7>预先准备好内存不够的情况
这一条款说的是:new可能调用失败,你要做好准备工作。
一、与C中类似的方法-宏:
#definenew(ptr, type) /
try {(ptr) = new type; } /
catch(std::bad_alloc&) { assert(0); }
此种方法缺点有2:
(1) 只是在没定义标准宏ndebug的时候,即在调试状态下才有效。
(2) 没有考虑到new有各种各样的使用方式。
二、当内存分配请求不能满足时,调用你预先指定的一个出错处理函数。
此方法基于一种机制:即当operator new不能满足请求时,会在抛出异常之前调用客户指定的一个出错处理函数。
指定出错处理函数时要用到set_new_handler函数,它的输入参数是operator new分配内存失败时要调用的出错处理函数的指针,返回值是set_new_handler没调用之前就已经在起作用的旧的出错处理函数的指针(有点类似GDI中设置dc的画笔)。下面是一个使用set_new_handler的例子。
//function to call if operator new can't allocateenough memory
void nomorememory()
{
cerr<< "unable to satisfy request for memory/n";
abort();
}
int main()
{
set_new_handler(nomorememory);
int*pbigdataarray = new int[100000000];
...... ...
}
operator new不能满足内存分配请求时,new-handler函数不只调用一次,而是不断重复,直至找到足够的内存(里面有一个while(1)循环)。
一个设计得好的new-handler函数必须实现下面功能中的一种:
<1>产生更多的可用内存。这将使operator new下一次分配内存的尝试有可能获得成功。实施这一策略的一个方法是:在程序启动时分配一个大的内存块,然后在第一次调用new-handler时释放。释放时伴随着一些对用户的警告信息,如内存数量太少,下次请求可能会失败,除非又有更多的可用空间。
<2>安装另一个不同的new-handler函数。如果当前的new-handler函数不能产生更多的可用内存,可能它会知道另一个new-handler函数可以提供更多的资源。这样的话,当前的new-handler可以安装另一个new-handler来取代它(通过调用set_new_handler)。下一次operator new调用new-handler时,会使用最近安装的那个。(这一策略的另一个变通办法是让new-handler可以改变它自己的运行行为,那么下次调用时,它将做不同的事。方法是使new-handler可以修改那些影响它自身行为的静态或全局数据。)
<3>卸除new-handler,抛出标准异常。也就是传递空指针给set_new_handler。没有安装new-handler,operator new分配内存不成功时就会抛出一个标准的std::bad_alloc类型的异常。
<4>抛出std::bad_alloc或从std::bad_alloc继承的其他类型的异常。这样的异常不会被operator new捕捉,所以它们会被送到最初进行内存请求的地方。(抛出别的不同类型的异常会违反operator new异常规范。规范中的缺省行为是调用abort,所以new-handler要抛出一个异常时,一定要确信它是从std::bad_alloc继承来的。想更多地了解异常规范,参见条款m14。)
<5>没有返回,直接中断退出。典型做法是调用abort或exit。abort/exit可以在标准c库中找到(还有标准c++库,参见条款49)。
二、写类自己的new和set_new_handler
简言之,分三步:1.class里面的"三样"要齐全。
(1) set_new_handler
(2) operator new
(3) static new_handler currenthandler;
2.实现set_new_handler。
(1) 保存传给它的任何指针,并返回在调用它之前所保存的任何指针。
(2) 保存传给它的任何指针,并返回在调用它之前所保存的任何指针。
3.实现operator new。
(1) 调用标准set_new_handler函数,输入参数为x的出错处理函数。这使得x的new-handler函数成为全局new-handler函数。注意下面的代码中,用了"::"符号显式地引用std空间(标准set_new_handler函数就存在于std空间)。
(2) 调用全局operatornew分配内存。如果第一次分配失败,全局operator new会调用x的new-handler,因为它刚刚(见1.)被安装成为全局new-handler。如果全局operator new最终未能分配到内存,它抛出std::bad_alloc异常,x的operator new会捕捉到它。x的operator new然后恢复最初被取代的全局new-handler函数,最后以抛出异常返回。
(3) 假设全局operatornew为类型x的对象分配内存成功,, x的operator new会再次调用标准set_new_handler来恢复最初的全局出错处理函数。最后返回分配成功的内存的指针。
c++是这么做的。
下面是一个"混合风格"(mixin-style)的基类,这种基类允许子类继承特定的功能:建立一个类的new-handler的功能。
template<classt> // 提供类set_new_handler支持的
classnewhandlersupport { // 混合风格"的基类
public:
static new_handlerset_new_handler(new_handler p);
static void * operator new(size_tsize);
private:
static new_handler currenthandler;
};
template<classt>
new_handlernewhandlersupport<t>::set_new_handler(new_handler p)
{
new_handler oldhandler =currenthandler;
currenthandler = p;
return oldhandler;
}
template<classt>
void *newhandlersupport<t>::operator new(size_t size)
{
new_handler globalhandler =
std::set_new_handler(currenthandler);
void *memory;
try {
memory = ::operatornew(size);
}
catch (std::bad_alloc&) {
std::set_new_handler(globalhandler);
throw;
}
std::set_new_handler(globalhandler);
return memory;
}
// this sets eachcurrenthandler to 0
template<class t>
new_handlernewhandlersupport<t>::currenthandler;
=====================================
<条款8>写operator new和operator delete时要遵循常规
一句话,系统缺省怎么做,你就怎么做。
一、new所要做的
1)要有正确的返回值;
2)可用内存不够时要调用出错处理函数(见条款7);
3)处理好0字节内存请求的情况;
4)避免不小心隐藏了标准形式的new(不过这是条款9的话题)。
这样, operator new的伪代码看起来会象下面这样:
void * operator new(size_t size) // operator new还可能有其它参数
{
if (size != sizeof(base)) // 如果数量"错误",让标准operator new
return ::operator new(size); // 去处理这个请求
while (1) {
分配size字节内存;
if (分配成功)
return (指向内存的指针);
// 分配不成功,找出当前出错处理函数
new_handler globalhandler = set_new_handler(0);
set_new_handler(globalhandler);
if (globalhandler) (*globalhandler)();
else throw std::bad_alloc();
}
}
三、delete的任务
所要记住的只是,c++保证删除空指针永远是安全的,所以你要充分地应用这一保证。
伪代码如下:
void base::operator delete(void *rawmemory, size_t size)
{
if (rawmemory == 0) return; // 检查空指针
if (size != sizeof(base)) { // 如果size"错误",
::operator delete(rawmemory); // 让标准operator来处理请求
return;
}
释放指向rawmemory的内存;
return;
}
=====================================
<条款9>避免隐藏标准形式的new
在类里定义了一个称为"operator new"的函数后,会不经意地阻止了对标准new的访
问。条款50解释了为什么会这样,这里我们更关心的是如何想个办法避免这个问题。
一个办法是在类里写一个支持标准new调用方式的operator new,它和标准new做同样的事。这可以用一个高效的内联函数来封装实现。
另一种方法是为每一个增加到operator new的参数提供缺省值(见条款24)。
=====================================
<条款10>如果写了operator new就要同时写operator delete
问题1:为什么自己写operator new 和 operator delete.(答:为了效率)
问题2:为什么要为自己的new写自己的delete.(答:因为标准delete不知道自己new出了多大内存,见《深度探索c++对象模型》)
问题3:内存池.(详情见eff c++)