(Effective C++)第八章 定制new和delete(Customizing new and delete)

10.1 条款49:了解new-handler行为 (Understand the behavior of the new-handler)
当operator new抛出异常以反映一个未获满足的内存需求之前,它会调用一个客户指定的错误处理函数,一个所谓的new-handler。标准程序库函数<new>的set_new_handler接口:
namespace std {
    typedef void (*new_handler)();
    new_handler set_new_handler(new_handler p) throw();
}
如上new_handler是个typedef,定义出一个指针指向函数,该函数没有参数也不返回任何东西。set_new_handler则是“获得一个new_handler并返回一个new_handler”的函数。Set_new_handler声明式尾端的“throw()”是一份异常明细,表示该函数不抛出任何异常。
Set_new_handler的参数是一个指针,指向operator new无法分配足够内存时该被调用的函数。其返回值也是个指针,指向Set_new_handler被调用前正在指向的那个new_handler函数。
设计一个良好的new_handler函数必须做到以下事情:
1)    让更多的内存可被使用。比如,程序一开始执行就分配一大块内存,而后当new_handler第一次被调用,将它们释放还给程序使用。
2)    安装另一个new_handler。寻找一个可以获得更多内存的new_handler。
3)    卸除new_handler,也就是将null指针传给set_new_handler。一旦不安装任何new_handler,operator new会在内存分配不成功时抛出异常。
4)    抛出bad_alloc(或派生自bad_alloc)的异常。
5)    不返回,通常调用abort或exit。
C++并不支持class专属之new_handlers,你需要设计一个Widget class来处理这种需求。

class Widget {
public:
  static std::new_handler set_new_handler(std::new_handler p) throw();
static void *operator new(std::size_t size) throw(std::bad_alloc);
private:
static std::new_handler currentHandler;
};
//static成员必须在class定义式之外被定义,在class实现文件内初始化为null
std::new_handler Widget::currentHandler = 0;
static std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldhandler = currentHandler;
currentHandler = p;
return oldhandler;
}
void* Widget::operator new (std::size_t size) throw(std::bad_alloc)
{  //安装Widget的new_hander
NewHandlerHolder h(std::new_handler(currentHandler));
return ::operator new(size);  //分配内存或抛出异常恢复global new_handler
}

示例10-1-1 class专属的new_handler上章
Widget的operator new做以下事情:
1)    调用标准set_new_handler,告知Widget的错误处理函数。
2)    调用global operator new,执行实际内存分配。如果分配失败,global operator new会调用Widget的new_handler。如果global operator new最终无法分配足够内存,会抛出一个bad_alloc异常。
3)    如果global operator new能够分配足够一个Widget对象所用的内存,Widget的operator new会返回一个指针,执行分配所得。
以下以C++代码再阐述一次:
    
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh):handler(nh){}  //取得目前的new_handler
~ NewHandlerHolder()
{ std::set_new_handler(handler);}  //释放它
  static std::new_handler set_new_handler(std::new_handler p) throw();
static void *operator new(std::size_t size) throw(std::bad_alloc);
private:
std::new_handler handler;
NewHandlerHolder(const NewHandlerHolder&);  //阻止coping行为,条款4
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
//static成员必须在class定义式之外被定义,在class实现文件内初始化为null
std::new_handler Widget::currentHandler = 0;
static std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldhandler = currentHandler;
currentHandler = p;
return oldhandler;
}
void outOfMem();
Widget::set_new_handler(outOfMem);
Widget *pw1 = new Widget; //如果分配失败,调用outOfMem
Std::string *ps = new std::string; //如果分配失败,调用global new_handler函数

Widget::set_new_handler(0);
Widget *pw2 = new Widget; //如果分配失败,立即抛出异常
示例10-1-2 class专属的new_handler下章
假设上述的方案改为base class继承derived class,然后将这个base class转为template,如此每个derived class将获得实体互异的class data复件。这个设计的base class部分让derived classes继承它们所需要的set_new_handler和operator new,而template部分则去报每一个derived class获得一个实体互异currentHandler成员变量。如下:
template<typename T>
class NewHandlerSupport { //class专属的set_new_handler
public:
  static std::new_handler set_new_handler(std::new_handler p) throw();
static void *operator new(std::size_t size) throw(std::bad_alloc);

private:
std::new_handler handler;
};
template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw();
{
std::new_handler oldhandler = currentHandler;
currentHandler = p;
return oldhandler;
}
template<typename T>
void* NewHandlerSupport<T>::operatornew (std::size_t size) throw(std::bad_alloc)
{  //安装Widget的new_hander
NewHandlerHolder h(std::new_handler(currentHandler));
return ::operator new(size);  //分配内存或抛出异常恢复global new_handler
}
//以下每一个currentHandler初始化null
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;
示例10-1-3 class专属的new_handler template
有了这个class template为Widget添加set_new_handler支持能力就容易多了。只要Widget继承NewhandlerSupport<Widget>就ok。
class Widget:: public NewHandlerSupport<Widget>
{ //和先前一样,但不必声明set_new_handler或operator new

};
Template机制会自动为每一个T生成一份currentHandler。
C++标准委员会提供了一种形式的operator new,负责供应传统的“分配失败就返回null”的行为。这个形式被称为“nothrow”形式。
Widget *pw1 = new Widget; //如果分配失败,抛出bad-alloc
if (pw1 == NULL) //这个测试一定失败
Widget *pw2 = new (std::nothrow) Widget; //如果分配失败,返回0
if (pw2 == NULL) //这个测试可能成功

表达式“new(std::nothrow)Widget ”发生两件事,第一,nothrow版的operator new被调用,如果分配失败,返回null。第二,Widget构造函数导致异常。因此,其实没有必要使用nothrow new的需要。


10.2 条款50:了解new和delete的合理替换时机 (Understand when it makes sense to replace new and delete)
替换编译器提供的operator new或operator delete的常见理由:
1)    用来检测运用上的错误。如果我们自行一个operator news,便可超额分配内存,以额外的空间放置特定的byte pattern(即签名,signature)。Operator deletes便得以检测上述签名是否原封不动。
2)    为了强化效能。 如果你对程序的动态内存分配有深刻了解,可以自己定制。
3)    为了收集统计数据。
4)    为了增加分配和归还的速度。泛用型的分配器往往比定制性分配器慢。
5)    为了降低内存管理器带来的空间额外开销。泛用型内存管理往往还是用更多的内存来区分类型。
6)    为了弥补缺省分配器中的非最佳齐位(suboptional alignment)。
7)    为了将相关对象成簇集中。
8)    为了获得非传统的行为。比如分配和归还共享内存(share memory)。使用C++封装C API。


10.3 条款51:编写new和delete时需要固守成规 (Be aware of template metaprogramming)
先从operator new谈起。实现一致性operator new必得返回正确的值,内存不足时必得调用new handling函数,必须有对付零内存需求的准备,还需要避免不慎掩盖正常形式的new。
Operator new的返回值,要么返回一个指针指向那块内存,要么抛出一个bad_alloc异常。Operator new实际上不止尝试一次分配内存,并每次失败后调用new_handing函数,当指向new_handing函数的指针是null,operator new才会抛出异常。Operator new内含一个无穷循环,退出循环的唯一办法是:内存分配成功,安装另一个new_handler,卸载new_handler,抛出bad_alloc异常,或是承认失败而直接return。条款49。
客户要求0bytes时,operator new也得返回一个合法指针,指向1字节的空间。
假设operator new成员函数会被derived classes继承。例如

class Base {
public:
static void *operator new(std::size_t size) throw(std::bad_alloc);   
。。。
};
class Derived:public Base {。。。}; //Derived没有声明operator new

Derived *pd = new Derived;  //错误,这里调用的是Base的operator new
示例10-3-1 operator new被继承
处理次清醒的最佳做法是将“内存申请量错误”的调用行为改为采用标准operator new,像这样:
void *Base::operator new(std::size_t size) throw (std::bad_alloc)
{
    if (size != sizeof(Base))
          return ::operator new(size);
    …
}
如果你决定写一个operator new[],唯一需要做的一件事就是分配一块未加工内存(raw memory)。
C++保证“删除null指针永远安全”,所以non-member operator delete的伪码:
void operator delete(void *pMem) throw ()
{
    if (pMem== 0) return;
    //现在归还内存
    …
}
这个函数的member版本如下:
void operator Base::delete(void *pMem, size_t size) throw ()
{
    if (pMem== 0) return;
if (size != sizeof(Base)){
     ::operator delete(size);
return;
}
//现在归还内存
    …
}


10.4 条款52:写了placement new也要写placement delete (Be aware of template metaprogramming)
当你写一个new表达式像这样:
Widget *pw = new Widget;
共有两个函数被调用:一个是用以分别内存的operator new,一个是widget 的default构造函数。
假设其中第一个函数调用成功,第二函数却抛出异常,此时防止内存泄露的责任落到C++运行期系统身上。运行期系统调用步骤一所调用的operator new的相应operator delete版本,前提是它必须知道哪一个operator delete该被调用。
正常的operator new:
void *operator new(std::size_t ) throw (std::bad_alloc);
对应于正常的operator delete:
void *operator delete(void* rawMem) throw (std::bad_alloc);  //global作用域
void *operator new(void* rawMem,  std::size_t size) throw (std::bad_alloc); //class作用域
如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这便是所谓的placement new。众多placement new版本中特别有用的一个是“接受一个指针指向对象该被构造之处”,如下:
void *operator new(std::size_t, void* pMem) throw();
这个版本的new被纳入了C++标准程序库。一般性术语,“placement new”意味带任意额外参数的new,因为另一个术语“placement delete”直接派生自它。
假设你写了一个class专属的operator new,如下:

class Widget {
public:

static void *operator new(std::size_t size, std::ostream & logStream)
   throw(std::bad_alloc);         //非正常形式的new
static void operator delete(void *pMem, std::size_t size);
  throw();                   //正常的class专属delete
};
//调用operator new并传递cerr为其ostream实参,
//这个动作会在widget构造函数抛出异常时泄露内存
Widget *pw = new (std::cerr) Widget;
示例10-4-1 class专属的operator new
运行期系统寻找“参数个数和类型都与operator new相同”的某个operator delete。如果找到,就调用它的调用对象。本例的operator new接受类型为ostream&的额外实参,所以对应的operator delete应该是:
void operator delete(void* , std::ostream &) throw();
类似于new的placement版本,operator delete如果接受额外参数,便称为placement deletes。
为此class Widget有必要声明一个placement delete,对应与那个有记录日志功能的placement new。
这样改变之后,有了如下的情况:
Widget *pw = new (std::cerr) Widget;
delete pw;  //调用正常的operator delete
如上情况,调用的是正常形式的operator delete,而非placement 版本。Placement delete版本只有在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。
另外,由于成员函数的名称会遮掩其外围作用域的相同名称(条款33),你必须小心避免让class的专属的news掩盖客户期望的其他news(包括正常版本)。
class Base {
public:

static void *operator new(std::size_t size, std::ostream & logStream)
   throw(std::bad_alloc);         //这个new会遮掩正常的global形式
。。。
};
Base *pb = new Base;  //错误,因为正常的operator new被遮掩
Base *pb = new (std::cerr) Base; //正确,调用Base的placement new

class Derived:public Base {
public:

static void *operator new(std::size_t size)
   throw(std::bad_alloc);         //重新声明正常的new
。。。
};
Derived *pd = new (std::clog) Derived;  //错误,因为Base的placement new被遮掩
Derived *pd = new Derived; //正确,调用Base的operator new

示例10-4-2 名称被遮掩
条款33,对于撰写分配函数,缺省情况下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
class StandardBase {
public:
// normal new/delete
static void *operator new(std::size_t size)
   throw(std::bad_alloc)
{ return ::operator new(size);}
static void operator delete(void* pMem) throw()
{return ::operator delete(pMem);}    
// placement new/delete
static void *operator new(std::size_t size,void*ptr) throw()
{ return ::operator new(size, ptr);}
static void operator delete(void* pMem, void*ptr) throw()
{ return ::operator delete(pMem,ptr); }
// nothrow new/delete
static void *operator new(std::size_t size, const std::nothrow_t &nt) throw()
{ return ::operator new(size, nt);}
static void operator delete(void* pMem, const std::nothrow_t &nt) throw()
{ return ::operator delete(pMem);}
};
class Widget:public StandardBase {
public:
using StandardBase::operator new;
using StandardBase::operator delete;
static void *operator new(std::size_t size, std::ostream & logStream)
   throw(std::bad_alloc);         //非正常形式的new
static void *operator delete(void* , std::ostream &) throw();
。。。
};
Widget *pw = new (std::clog) Widget;  //OK
Widget *pw = new Widget; //正确
示例10-4-3 class专属new用法
这就意味着,如果要对所有与placement new相关的内存泄露宣战,必须同时提供一个正常的operator delete(用于构造期间无任何异常被抛出)和一个placement版本(用于构造期间有异常抛出)。后者的额外参数必须和operator new一样。

11 其他(Others)

11.1 C++关键字explicit
C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。
  C++中, 一个参数的构造函数, 承担了两个角色。 1 是个构造器 2 是个默认且隐含的类型转换操作符。
  所以, 有时候在我们写下如 AAA = XXX, 这样的代码, 且恰好XXX的类型正好是AAA单参数构造器的参数类型, 这时候编译器就自动调用这个构造器, 创建一个AAA的对象。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值