东阳的学习笔记
- 多线程环境下的内存管理遭受单线程系统不曾有过的挑战。
- STL所使用的heap内存是由容器所拥有的分配器对象(allocator objects)管理
条款49:了解new-handler的行为
- 当operator new 抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的 new-handler。为了指定这个 “用以处理内存不足的”函数,客户必须调用
set_new_handler
,其声明于头文件 。
namespace std {
typedef void (*new_handler) ( );
new_handler set_new_handler(new_handler p) throw();
}
- set_new_handler 的参数是个指针,指向 operator 无法分配足够内存时该被调用的函数。其返回值也是个指针。
void outOfMem()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
std::set_new_handler(outOfMem); // 当 new 无法分配足够内存时调用
int *pBigDataArray = new int[10000000000L];
std::cout << "i am alive" << std::endl;
return 0;
}
- 一个设计良好的 new-handler 必须做以下事情:
i. 让更多的内存可被使用。实现此策略的一个办法是:程序一开始就分配一大块内存(提前占用),当new_handler 第一次被调用时,将他们释放返还给程序使用。
ii. 安装另一个 new_handler。为达此目的,做法之一就是让当前 new_handler 修改“会影响new_handler行为” 的static数据、namespace数据、global数据等。
iii. 卸载 new_handler。也就是将 null 指针传给 set_new_handle。一旦没有安装任何的 new_handler,下次调用就会抛出异常。
iv. 抛出 bad_alloc,将异常传播到内存索求处
v. 不返回,通常调用abort() / exit() - DO IT FOR ME:
class Widget: public NewHandlerSupport<Widget> { ... }
- 旧版本的C++,分配失败就返回
null
:(使用std::nothrow
声明使用返回null的版本)
Widget *pw2 = new ( std::nothrow ) Widget; // 如果分配失败,则返回0
if (pw2 == 0) ... // 侦测 null
- Nothrow new 是一个颇为局限的工具,因为它只适用于内存分配;后面的
构造函数调用还是可能抛出异常
条款50:了解 new / delete 的合理替换时机
- 什么时候需要替换编译器提供的 new / delete 呢?以下是三个常见的理由:
i. 用来检测运用上的错误。比如我们自行定义一个 new,以额外空间(位于客户所得区块之前或后)放置特定的 byte pattern (即签名,signatures)。delete 通过检查签名是否原封不动来判断是否发生了overrun
或underrun
,并可志记(log)下来。
ii. 为了强化效能。编译器自带的 new / delete 采取中庸之道
。不对特定任何人有最佳表现。通过定制版本
,是获得最大效能提升的方法之一。
iii. 为了收集使用上的统计数据。了解程序对内存的使用习惯。 - 编写自己的 new / delete时注意
齐位
- operator new 都应该包含一个循环,反复调用某个
new_handling 函数
。 - 何时替换缺省的 new / delete:
i. 为了增加分配和归还的速度
ii. 为了降低缺省内存管理器带来的额外空间开销
iii. 为了弥补缺省内存管理器中的非最佳齐位
iv. 为了将相关对象成簇集中(在更少的内存页上)
v. 为了获得非传统的行为
vi. 为了检测运用错误
vii. 为了收集使用统计信息
条款51:编写 new / delete时需固守常规
- new 不只一次尝试分配内存,并在每次失败后调用
new-handling
函数 - operator new 应该包含一个无限循环,退出循环的唯一方法是:
内存分配成功
或抛出异常
- 如果你打算控制class专属之 array 控制行为,那么你需要实现 operator new []。记住:
唯一要做的事就是分配一块未加工内存(row memory)
。因为你无法知道每个对象有多大(在即成体系中, base / derived 的大小不一样) - delete 更简单:你需要记住的唯一事情就是C++保证删除 null 指针永远安全。
如果删除的是个null指针,则什么也不做
void operator delete(void *rawMemory) throw()
{
if (rawMemory == 0) return; // 如果删除的是个null指针,则什么也不做
}
- member版本的 delete,只需多一个动作检查删除数量。万一你的 class 专属的 operator new 将大小有误的分配行为转交给 ::operator new执行,你也必须将删除行为转交给 ::operator delete执行。
条款52:写了placement new 也要写 placement delete
Widget *pw = new Widget;
- 上述语句共调用了两个函数:new 和 构造函数
- 假设第一个函数调用成功,第二个函数却抛出异常。这样将会导致 pw 未被成功赋值,客户手上也没有能够指针指向该被归还的内存。
取消步骤一并恢复旧观的责任因此落到C++运行期系统上
- 运行期系统会高高兴兴地调用步骤一调用的 new 的相应 delete 版本。
void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void* rawMemory) throw();//global 作用域中的正常签名式
void operator delete(void* rawMemory, std::size_t size) throw();//class作用域中的典型签名式。
- 当你自定义自己的 new 时, 也就是带了附加参数的 new 时,究竟哪一个 delete 应该被调用就是一个问题了
class Widget{
public:
static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
static void* operator delete(void* pMemory, std::size_t size) throw();
};
- 如上例,一个有额外参数的 new 却定义了一个 正常的 delete。此时C++运行期系统无法正常 delete,因此需要定义一个与
自定义new
对应的placement delete
。此时,对应的placement delete
会被调用,抛出异常,但是,此时却并不会泄漏任何内存
class Widget{
public:
static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
static void* operator delete(void* pMemory, std::size_t size) throw();
static void* operator delete(void* pMemory, std::ostream& logStream) throw();
};
placement delete
只有在 “伴随placement new
调用而触发的构造函数”出现异常时才会被调用,对一个指针施行 delete绝对不对调用
placement delete
- 由于成员函数的名称会
外围作用域
中的相同名称,因此:
i. 建立一个 base class 内含所有正常形式的 new / delete
ii. 继承上述类,并使用 using 声明
class StandardNewDeleteForms{
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* pMemory) throw()
{::operator delete(pMemory);}
//placement new/delete
static void* operator new(std::size_t size, void* ptr) throw()
{return ::operator new(size, ptr);}
static void operator delete(void* pMemory, void* ptr)throw()
{return ::operator delete(pMemory, 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* pMemory, const std::nothrow_t&) throw()
{::operator delete(pMemory);}
};
class Widget: public StandardNewDeleteForms{
public:
using StandardNewDeleteForms::operator new;
using StandardNewDeleteForms::operator delete;
static void* operator new(std::size_t size, std::ostream& logStream)throw(std::bad_alloc);
static void operator delete(void* pMemory, std::ostream& logStream) throw();
};
当你声明 placement new / delete,请确定不要无意识(非故意)地遮掩了他们的正常版本