Effective C++笔记(11)—定制new和delete
Posted NearXDU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Effective C++笔记(11)—定制new和delete相关的知识,希望对你有一定的参考价值。
定制new和delete
这部分主要介绍Operator new
和Operator delete
。
回顾一下,一直熟知的new/delete其实全称是new operator
/delete operator
是操作符。以new
例,它包含了两个操作:第一部分是分配足够的内存以便容纳所需类型的对象。第二部分是它调用构造函数初始化内存中的对象。new操作符总是做这两件事情,你不能以任何方式改变它的行为。
条款49:了解new-handler的行为
operator new 无法满足某一内存分配需求的时候,它会抛出异常。
再抛出异常之前,它会先调用一个客户指定的错误处理函数即new-handler
namespace std
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
;
可以看出new_handler是一个函数指针类型,该函数指针指向的函数返回空参数也为空。而set_new_handler
的操作是从参数设置一个新的new_handler
并返回一个旧的new_handler
。
在STL中有set_new_handler(0);
就是强制卸载异常处理函数,当内存分配不够时直接抛出std::bad_alloc异常。
void outOfMem()
std::cerr << "out of mem" << endl;
throw bad_alloc();
//std::abort();
int main()
std::set_new_handler(outOfMem);
while (true)
int *p = new int[100000];
system("pause");
return 0;
设计良好的new-handler,本条款给出以下结论:
1.让更多内存可被使用
2.安装另一个new-handler(调用set_new_handler
)
3.写出new-handler:set_new_handler(0);
4.抛出异常:throw std::bad_alloc();
5.直接std::abort()
首先是一个RAII管理的例子。
class NewHandlerHolder :public boost::noncopyable
public:
explicit NewHandlerHolder(new_handler nh) :handler(nh)
~NewHandlerHolder() set_new_handler(handler);
private:
new_handler handler;
;
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之外进行初始化
std::new_handler currentHandler = 0;
std::new_handler Widget::set_new_handler(std::new_handler p)throw()
new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
void *Widget::operator new(size_t size) throw(bad_alloc)
NewHandlerHolder h(std::set_new_handler(currentHandler));
return ::operator new(size);
之后作者用了模板对该例子进行了扩充:
///1.RAII
class NewHandlerHolder :public boost::noncopyable
public:
explicit NewHandlerHolder(new_handler nh) :handler(nh)
~NewHandlerHolder() set_new_handler(handler);
private:
new_handler handler;
;
//模板
template<class T>
class NewHandlerSupport
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;
;
template<class T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p)throw()
new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
template<class T>
void *NewHandlerHolder::operator new(size_t size) throw(bad_alloc)
NewHandlerHolder h(std::set_new_handler(currentHandler));
return ::operator new(size);
使用的时候只需要继承于模板类即可:
class Widget : public NewHandlerSupport<Widget>
;
这里的类型参数T只是用来区分Derived class,由于模板的机制,会为每个T生成一份currentHandler
这种技术叫做:CRTP(curiously recurring template pattern)怪异的循环模板模式。
条款50:了解new和delete的合理替换时机
什么时候替换operator new
或者operator delete
书中给了几个理由:
1.检测运用时的错误
new/delete要配合使用否则会内存泄漏;
delete大于1次则会造成不定义行为。
另外还有可能有overrun和underrun错误。
overrun:写入点在分配区块尾端之后
underrun:写入点在分配区块起点之前
2.强化效能
定制版的operator new/operator delete性能胜过缺省版本,需要的内存也比较少,最高可以省50%。
3.收集使用上的统计数据
使用定制版的Operator new/operator delete还可以获取到一些信息:比如说分配区块的大小分布?寿命?倾向于FIFO或者是LIFO等等。。
书中给出一个例子,比如说,我们希望在operator new时在申请的空间加入两个int字节存放签名:
第一个强转直接赋值;
第二个强转是先计算signature的存放位置,再将值赋进去。
static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;
void *operator new(std::size_t size) throw(std::bad_alloc)
using namespace std;
size_t realSize = size + 2 * sizeof(int);
void *pMem = malloc(realSize);
if (!pMem)
throw bad_alloc();
*(static_cast<int*>(pMem)) = signature;
*(reinterpret_cast<int*>(static_cast<Byte*>(pMem)+realSize - sizeof(int))) = signature;
return static_cast<Byte*>(pMem)+sizeof(int);
条款51:编写new和delete时需要固守的常规
当new_heandler指向null时,抛出异常,否则每次失败后都会调用new-handling函数。
void *operator new(std::size_t size) throw (std::bad_alloc)
using namespace std;
if (size == 0)
size = 1;
while (true)
void * p = malloc(size);
if (p!=nullptr)//success
return p;
//作者这样做的意图是获取当前的handler,不过在C++11标准中有get_new_handler这样的调用了。
new_handler globalHandler = set_new_handler(0);//先拿到当前的handler
set_new_handler(globalHandler);//set当前的handler为newhandler
if (globalHandler)
(*globalHandler)();
else//直到Handler 为 null 抛出异常。
throw bad_alloc();
上述情况为考虑类继承的情况,假设我们为base类定义了一个Operator new当derived类没有定义自己的operator new那么这个函数将被继承下来,由于基类和继承类某些不一样的特性,这样的调用就会出现问题:
class Base
public:
static void * operator new (std::size_t size) throw(std::bad_alloc)
cout << "base::new" << endl;
return nullptr;
;
class Derived :public Base
;
int main()
Derived *p = new Derived;// base::new
if(p==nullptr)
cout<<"worked"<<endl;//ok
system("pause");
return 0;
如果Base class专属的operator new并非被设计用来对付上述情况,处理此情形的最佳做法是,当内存申请错误,改调用标准的Operator new
class Base
public:
static void * operator new (std::size_t size) throw(std::bad_alloc)
if (size != sizeof(Base))
return ::operator new(size);
else
cout << "base::new" << endl;
return nullptr;
;
class Derived :public Base
int x;
;
int main()
Derived *p = new Derived;// base::new
if (p == nullptr)
cout << "custom worked" << endl;//ok
else
cout << "standard work" << endl;
system("pause");
return 0;
比起operator new
,operator delete情况更加简单。
void operator delete(void *m)throw()
if(m==0) return;
free m;
在class中设计也是类似:
static void operator delete(void *rawMem, std::size_t size)throw()
if (rawMem == 0)return;
if (size != sizeof(Base))
::operator delete(rawMem);
return;
// 归还
return;
与operator new类似,class专属的delete将大小有误的释放行为,转交给标准的delete。
当base没有虚析构函数,C++传给operator delete的大小可能不正确,就有可能调用标准的delete函数,operator delete可能就无法正确运作了。
条款52:写了placement new也要写placement delete
Widge *pw=new Widge;
上述调用分为两部分:
1.分配内存Operator new
2.是调用构造函数
如果构造函数抛出异常,需要将第一步分配的内存释放,否则会资源泄漏。
对于正常形式的new/delete C++运行期系统将会搞定。
如果开始声明了非正常形式的operator new,事情就变得复杂了。
在以前学习STL中,对于placement new的认为是,在以申请的的内存进行初始化,例如:
//stl_construct.h部分
template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value)
new ((void*) __p) _T1(__value);//指定调用_T1的构造函数_T1::_T1(__value)来初始化指针__p所指向的内存 (placement new)
template <class _T1>
inline void _Construct(_T1* __p)
new ((void*) __p) _T1();//与上述类似,默认构造函数
在本条款中,给出的定义:
如果operator new
接受的参数除了一定会有的那个size_t之外还有其他,这个是所谓的placemet new。
比如说:
void *operator new(std::size_t ,void *pmem)throw();
void *operator new(std::size_t ,ostream&logstream)throw();
这个可能是更为广义的定义。
Widget *pw = new(cerr) Widget;
如果我们想在构造的Widget时传入ostream实参,那么可以按上述写法,同样的,如果构造失败,我们必须释放内存,此时不会调用标准的版本,需要我们自己写一个operator delete,这就是本条款的核心:
void opertor delete(void *,ostream&)throw;
这个规则很简单,就是当我们使用了带有额外参数的operator new我们也要写个带同样参数的operator delete.
在继承体系中,class的 placement new往往会遭遇遮掩的问题,作者给了一解决方案:
class StandardNewDeleteForm
public:
//标准 new/delete
//placement new/delete
//nothrow new/delete
;
class Widget :public StandardNewDeleteForm
public:
using StandardNewDeleteForm::operator new;
using StandardNewDeleteForm::operator delete;
//添加自定义的placement new/delete
;
以上是关于Effective C++笔记(11)—定制new和delete的主要内容,如果未能解决你的问题,请参考以下文章