本章主要唠嗑一下new和delete。
条款四十九:了解new-handle的行为当operator new抛出异常以反应未获满足的内存需求之前会先调用new-handle。客户可以指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler。(在标准库)
#include
using namespace std;
const int SIZE = 1024*1024;
char* buffer = NULL;
//没有内存了,set_new_handler(0),不再反复调用new-handler
void outOfMemHandler()
{
//2.安装另一个new - hander。
cout << "Out of memory" << endl;
//3.卸除new - handle
set_new_handler(0);
}
//释放一些内存,确保下次能够分配内存
void newHandlerFree()
{
if (NULL != buffer)
{
//1.让更多内存被使用。
cout << "Free some memory(buffer)" << endl;
delete buffer;
buffer = NULL;
return;
}
cout << "No more memory for free" << endl;
set_new_handler(outOfMemHandler);
}
int main()
{
//buffer将会在后续内存分配失败后释放
//这里buffer的size随着不同的环境调整,主要是为了制造没有足够内存分配
buffer = new char[SIZE * 4999];
set_new_handler(newHandlerFree);
try
{
while (true)
{
new char[SIZE * 4999];
cout << "Memory allocation successful" << endl;
}
}
catch (const std::bad_alloc& e)
{
//5.抛出bad - alloc。
cout << "Catch exception: " << e.what() << endl;
//6.不返回
abort();
}
}
本例代码就是不断生成内存直到用尽,而抛出异常。为了防止当operator无法满足内存申请时反复调用new-handler函数,我们需要做以下事情:
1.让更多内存被使用。(调用new-handler之后将资源释放)
2.安装另一个new-hander。(当前的new-hander无法取得更多内存,则需要用另一个new-hander替代自己)
3.卸除new-handle(将null指针传给set_new_hander。使得抛出异常)
5.抛出bad-alloc。(这个异常不会被operator new捕捉,因此会被传播到内存索求处)
6.不返回。(用abort或exit)
C++不支持class专属的new-handers。
假设我们打算处理Widge内存分配失败的情况。
那么Widge的operator new应该做这些事情:
1.调用标准的set_new_hander,告知Widge的错误处理函数。这会将Widge的new-handler安装为global new-handler。
2.调用global operator new,执行实际之内存分配。如果分配失败,global operator new 会调用Widge的new-handler,因为那个函数才刚被安装为global new-hander。如果global operator new最终无法分配足够内存,会抛出一个bad-alloc异常。在此情况下Wdige的operator new必须恢复原本的global new-hander,然后再传播该异常。为确保原本的new-hander总是能够被重新安装回去,Widge将global new-handler视为资源,运用资源管理器防止资源泄漏。
3.如果global operator new 能够分配足够一个Widge对象所用内存,那么会返回一个指针,指向分配所得。Widge析构函数会管理global new-handler,它会自动将Widget’s operator new被调用前的那个global new-handler恢复回来。
#include
#include
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh) :handler(nh) {} //取得目前的new_handler
~NewHandlerHolder() { std::set_new_handler(handler); } //释放它,回复原本的global-new-handler
private:
std::new_handler handler;
NewHandlerHolder(const NewHandlerHolder&); //阻止copying
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
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 currentHander; //new_handler只是一个函数指针,typedef void (*new_handler)()
};
//static成员必须在class定义时之外被定义(除非它们是const而且是整数型,见条款2),所以需要这么写:
std::new_handler Widget::currentHander = 0; //在class实现文件内初始化为null
//Widget内的set_new_handler函数会将它获得的指针存储起来,然后返回先前(在调用之前)存储的指针,这也正是标准版set_new_handler的作为
std::new_handler Widget::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHander;
currentHander = p;
return oldHandler;
}
void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolder h(std::set_new_handler(currentHander)); //安装Widget的new_handler分配内存或者抛出异常
return ::operator new(size); //回复global new_handler,如果失败,首先恢复原本的global-new-handler,再传播异常,成功的话返回一个指针
}
void outOfMem()//函数声明,此函数在Widget对象分配失败时被调用
{
std::cout << "Out of memory" << std::endl;
}
int main()
{
Widget::set_new_handler(outOfMem);//设定outOfMem为Widget的new-handling函数
Widget* pw1 = new Widget;//如果内存分配失败,调用outOfMem
std::string* ps = new std::string;//如果内存分配失败,调用global new-handling函数(如果有的话)
Widget::set_new_handler(0);//设定Widget专属的new-handling函数为null
Widget* pw2 = new Widget;//如果内存分配失败,立刻抛出异常(class Widget并没有专属的new-hangling函数)
return 0;
}
可以加入模板使得可以复用。
#include
#include
class NewHandlerHolder
{
public:
explicit NewHandlerHolder(std::new_handler nh)
:handler(nh) {
}
~NewHandlerHolder() {
std::set_new_handler(handler);
}
private:
std::new_handler handler;
//阻止拷贝 *** 作
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
template<typename T>
class NewHandlerSupport {
public:
static std::new_handler set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHander;
currentHander = p;
return oldHandler;
}
static void* operator new(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolder h(std::set_new_handler(currentHander)); //调用标准的set_new_hander,告知Widge的错误处理函数。
return ::operator new(size); //调用global operator new,执行实际之内存分配。如果失败,首先恢复原本的global-new-handler,再传播异常,成功的话返回一个指针
}
private:
std::new_handler handler;
static std::new_handler currentHander; //new_handler只是一个函数指针,typedef void (*new_handler)()
};
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHander = 0;
class Widget:public NewHandlerSupport<Widget> {
public:
Widget() {
}
~Widget() {
}
};
void outOfMem()//函数声明,此函数在Widget对象分配失败时被调用
{
std::cout << "Out of memory" << std::endl;
}
int main()
{
Widget::set_new_handler(outOfMem);//设定outOfMem为Widget的new-handling函数
Widget* pw1 = new Widget;//如果内存分配失败,调用outOfMem
std::string* ps = new std::string;//如果内存分配失败,调用global new-handling函数(如果有的话)
Widget::set_new_handler(0);//设定Widget专属的new-handling函数为null
Widget* pw2 = new Widget;//如果内存分配失败,立刻抛出异常(class Widget并没有专属的new-hangling函数)
return 0;
}
直至1993年,C++还要求operator new必须在无法分配足够内存的时候返回null。但现在则应该抛出bad_alloc异常,但C++委员会不想抛弃那些”侦测null”的族群,故引入了“nothrow形式”---------某种程度上是因为他们在new的使用场合用了nothrow对象。
#include
#include
class Widget {};
int main()
{
Widget* pw1 = new Widget;//如果内存分配失败,抛出bad_alloc
if (pw1 == 0)
{
//不可能运行这句
}
Widget* pw2 = new (std::nothrow)Widget;//如果内存分配失败,返回0
if (pw2)
{
//这句会在内存分配失败的时候运行
}
return 0;
}
这个表达式发生了什么?
nothrow版的operator new 被调用,用以分配足够内存给Widge对象。如果分配失败便返回null指针。如果分配成功,则Widge的构造函数会被调用。但其实构造函数可能也会抛出异常,所以nothrow new不能保证operator new不抛异常。因此完全不需要用nothrow
请记住:
set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
Nothrow new 是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常的。
为什么有人想替换编译器提供的new和delete?因为:
- 用来检测运用上的错误。方便log日志之类的,查看new分配的地址。
- 为了强化效能。可以自己定制new/delete以加快性能和内存使用。
- 为了收集使用上的统计数据。收集某些信息,比如分配区块大小分布,寿命分布,分配次序等。
我们可以简单的写一个自己的new
#include
#include
static const int signture = 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);//增加大小使得能塞入两个sintrue
void* pMem = malloc(realSize);
if (!pMem) throw bad_alloc();
//写入内存的最前段落和最后段落
*(static_cast<int*>(pMem)) = signture;
*(reinterpret_cast<int*>(static_cast<Byte*>(pMem)) + realSize - sizeof(int)) = signture;
/*signture + Byte* + signture */
//返回指针,恰好位于第一个signture之后的内存位置
return static_cast<Byte*>(pMem) + sizeof(int);
}
但这个函数有很多缺点:
- 并没有反复调用某个new-handing函数的机制
- 没有满足齐位。某些地方要求指针地址是4倍数或doubles的地址必须是8倍数。
本条款的主题是,了解何时可在”全局性的”或“class专属的”基础上合理替换缺省new和delete。挖掘更多细节之前,我先对答案进行一些摘要:
- 为了检测运用错误
- 为了收集动态分配内存之使用统计信息
- 为了增加分配和归还的速度。
- 为了降低缺省内存管理器带来的空间额外开销。
- 为了弥补缺省分配器中的非最佳齐位。
- 为了将相关对象成簇集中
- 为了获得非传统的行为
请记住:
有许多理由需要写个自定的new和delete包括改善性能,对heap运用错误进行调试,收集heap信息。
我们需要遵守的规则:
- 内存不足的时候必须调用new-handing函数
- C++规定即使用户要求0bytes,operator new也得返回一个合法的指针。一般是把0byte视为1byte
- 将大小有误的释放行为,转交给标准的delete。
- operator new[]唯一需要做的一件事就是分配一块未加工内存.
- operator delete应该在收到null指针时,不做任何事.class专属版本则还应该处理"比正确大小更大的错误申请"
请记住:
operator new 应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler。它也应该有能力处理0byte申请。Class专属版本则还应该处理”比正确大小更大的(错误)申请”
operator delete应该在收到null指针时不做任何事。Class专属版本则还应该处理“比正确大小更大的(错误)申请”
- 如果operator new接受的参数除了一定会有的那个size_t之外,还有其他参数,这便是个所谓的placement new.
- 相应的placement delete指"参数个数和类型都与operator new相同的"operator delete
- 运行期,若replacement new抛出异常,则系统会去找与placement new相对应的placement delete版本,若找不到则什么也不做.
但是placement delete只有在"伴随placement new调用而触发的构造函数"出现异常时才会被编译器自动调用.对一个指针显式调用delete不会导致调用placement delete. - 缺省情况下,C++在global作用域提供三种形式的operatornew
* 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 - 当声明了专有类的new和delete之后,请注意,它会遮掩std的标准new和delete.
- 为了避免class的专属news掩盖其他的news。我们可以使用继承。在base class中包含所有正常形式的new和delete。凡是想要以自定义扩充标准形式的客户可以利用继承机制和using声明式来取得标准形式。
class StandardNewDeleteForms{
public:
static void* operator new(std::size_t size)noexcept(std::bad_alloc)
{ return ::operator new(size); } //交给global new处理
static void operator delete(void* pMemory)noexcept
{ ::operator delete(pMemory); }
...
//因为标准new与delete有三个版本 normal new/delete,placement new/delete, nothrow new/delete,这边就不写了
};
class Widget:public StandaedNewDeleteForms{
public:
using StandardNewDeleteForm::operator new; //让标准形式可见
using StandardNewDeleteForm::operator delete;
...
};
请记住:
当你写一个placement operator new,请确定也写出了对应的placement operator delete。如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄漏。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)