Effective C++ 条款49、50

条款49 了解new-handler的行为

1、当operator new抛出异常以反映一个未获满足的内存需求之前邮寄费会先调用一个客户指定的错误处理函数,一个所谓的new-handler。为了指定这个“用以处理内存足”的函数,客户必须调用set_new_handler。关于<new>的一个标准程序库函数:

namespace std {
	typedef void(*new_handler)();
	new_handler set_new_handler(new_handler p)throw();
}

set_new_handler的用法:set_new_handler的参数是个指针,指向operator new无法分配足够内存时该被调用的函数。其返回值也是个指针,指向set_new_handler被调用前正执行(但马上就要替换)的那个new_handler函数。

set_new_handler用法举例:

//以下是operator new无法分配足够内存时,该被调用的函数
void outOfMen() {
	std::current_exception << "Unable to satisfy request for memory\n";
	std::abort();
}
int main() {
	std::set_new_handle(outOfMem);
	int* pBigDataArray = new int[1000000000L];
}

当operator new无法满足内存申请时,它会不断调用new-handler函数,直至找到足够内存。

一个设计良好的new-handler函数必须做以下事情:

i、让更多内存可被使用

ii、安装另一个new-handler

iii、卸除new-handler

iv、抛出bad_alloc,也就是将null指针传给set_new_handler

v、不返回,通常调用abort或exit

上述事情,一眼看去是懵B的,每隔三五天回过头看也是懵B,我也不知道为什么。

下面是一个Widget class内存分配失败情况的举例:

首先登陆“当operator new无法为一个Widget对象分配足够内存时”调用的函数,所以需要声明一个类型为new_handler的static成员,用以指向class Widget的new-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 Widget::currenthandler = 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 = currenthandler;
	currenthandler = p;//保存获取指向函数的指针
	return oldHandler;//返回之前存储的指针
}

最后,Widget的operator new做以下事情:

i、调用标准set_new_handler,告知Widget的错误处理函数。这会将Widget的new_handler安装为global new-handler

ii、调用global operator new,执行内存分配。

iii、如果global operator new能够分配足够一个Widget对象所用的内存,Widget的operator new会返回一个指针,指向分配所得。

2、从资源处理类(RAII)角度再次阐述:

class NewHandlerHolder {
public:
	explicit NewHandlerHolder(std::new_handler nh)://取得目前new-handler
		handler(nh){}
	NewHandlerHolder() {      //释放它
		std::set_new_handler(handler);
	}
private:
	std::new_handler handler;//记录下来
	NewHandlerHolder(const NewHandlerHolder&);//阻止copying
	NewHandlerHolder& operator=(const NewHandlerHolder&);
};

//对类Widget内的new操作符进行定义
void* Widget::operator new(std::size_t size) throw(std::bad_alloc) {
	NewHandlerHolder h(std::set_new_handler(currenthandler));//安装Widget的new-handler.
	return ::operator new(size);                             //分配内存或抛出异常。恢复global new-handler
}
于是Wiget的客户可以如下使用其new-handling:
void outOfMem();//函数声明。此函数在Widget对象分配失败时被调用

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  0表明关闭outOfMem函数的调用

Widget* pw2 = new Widget;//如果内存分配失败,立刻抛出异常。

3、Nothrow new是一个颇为局限的工具,因为它只适合内存分配;后继的构造函数调用还是可能抛出异常

用法举例:

Widget* pw2 = new (std::nothrow) Widget;
上述语句能抛出异常可从两部分入手分析,i、一个new过程可能发生异常;ii、一个Widget构造函数,可能发生异常。而std::nothrow只能保证i过程发生异常返回NULL,而ii过程不能保证。

条款50 了解new和delete的合理替换时机

替换编译器提供的operator new或operator delete的常见三个理由:

i、用来检测动用上的错误;(内存泄漏不确定行为)

ii、为了强化效能;(内存碎片,无法满足大区块内存要求,但却有足够但散为许多小区块的自由内存)

iii、为了收集使用上的统计数据。

有许多理由需要写个自定义的new和delete,包括改善效能、对heap运用错误进行调试、收集heap使用信息。

以上内容均来自Scott Meyers大师所著Effective C++ version3,如有错误地方,欢迎指正!相互学习,促进!!

Effective.C++.中文第二版,大小 1 Mb,chm 格式,作者:Scott Meyers,翻译:Lostmouse。 内容预览: 第一章 从C转向C++ 条款1:尽量用const和inline而不用#define 条款2:尽量用<iostream>而不用<stdio.h> 条款3:尽量用new和delete而不用malloc和free 条款4:尽量使用c++风格的注释 第二章 内存管理 条款5:对应的new和delete要采用相同的形式 条款6:析构函数里对指针成员调用delete 条款7:预先准备好内存不够的情况 条款8: 写operator new和operator delete时要遵循常规 条款9: 避免隐藏标准形式的new 条款10: 如果写了operator new就要同时写operator delete 第三章 构造函数,析构函数和赋值操作符 条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符 条款12: 尽量使用初始化而不要在构造函数里赋值 条款13: 初始化列表中成员列出的顺序和它们在类中声明的顺序相同 条款14: 确定基类有虚析构函数 条款15: 让operator=返回*this的引用 条款16: 在operator=中对所有数据成员赋值 条款17: 在operator=中检查给自己赋值的情况 第四章 类和函数:设计与声明条款 条款18: 争取使类的接口完整并且最小 条款19: 分清成员函数,非成员函数和友元函数 条款20: 避免public接口出现数据成员 条款21: 尽可能使用const 条款22: 尽量用“传引用”而不用“传值” 条款23: 必须返回一个对象时不要试图返回一个引用 条款24: 在函数重载和设定参数缺省值间慎重选择 条款25: 避免对指针和数字类型重载 条款26: 当心潜在的二义性 条款27: 如果不想使用隐式生成的函数就要显式地禁止它 条款28: 划分全局名字空间 第五章 类和函数: 实现 条款29: 避免返回内部数据的句柄 条款30: 避免这样的成员函数:其返回值是指向成员的非const指针或引用,但成员的访问级比这个函数要低 条款31: 千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引 条款32: 尽可能地推迟变量的定义 条款33: 明智地使用内联 条款34: 将文件间的编译依赖性降至最低 第六章 继承和面向对象设计 条款35: 使公有继承体现 "是一个" 的含义 条款36: 区分接口继承和实现继承 条款37: 决不要重新定义继承而来的非虚函数 条款38: 决不要重新定义继承而来的缺省参数值 条款39: 避免 "向下转换" 继承层次 条款40: 通过分层来体现 "有一个" 或 "用...来实现" 条款41: 区分继承和模板 条款42: 明智地使用私有继承 条款43: 明智地使用多继承 条款44: 说你想说的;理解你所说的 第七章 杂项 条款45: 弄清C++在幕后为你所写、所调用的函数 条款46: 宁可编译和链接时出错,也不要运行时出错 条款47: 确保非局部静态对象在使用前被初始化 条款48: 重视编译器警告 条款49: 熟悉标准库 条款50: 提高对C++的认识
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值