- 条款49:了解new-handler的行为
- 条款50:了解new和delete的合理替换时机
- 条款51:编写new和delete时需固守常规
- 条款52:写了placement new也要写placement delete
以下operator new和operator delete的规则同样适用于operator new[]和operator
delete[]
条款49:了解new-handler的行为
- 当operator new无法满足某一内存分配需求时,就会抛出异常。在它抛出异常前,会先调用一个用户指定的错误处理函数,一个所谓的
new-handler
,为了指定这个函数,用户需要调用set_new_handler
,那是声明于的一个标准程序库函数: 一个设计良好的new-handler函数必须做以下事情:1
2
3
4
5
6
7namespace std {
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
// 使用时
std::set_new_handler(outOfMem);- 让更多内存可被使用。让operator new的下一次内存分配动作可能成功,就让程序一开始执行分配一大块内存,当new-handler第一次被调用,将它们释放给程序使用。
- 安装另一个new-handler。如果目前这个new-handler无法取得个更多可用内存,或许其他new-handler有。那么这个new-handler则set_new_handler来替换自己。下次当operator new调用new-handler时,调用的将是最新安装的那个。
- 卸除new-handler。一旦没有安装任何new-handler,operator new会在内存分配不成功时抛出异常,这时候应该将null指针给set_new_handler。
- 抛出bad_alloc(或派生自bad_alloc)的异常。这样异常不会被operator new捕捉,会被传播到内存索求处。
- 不返回。调用abort或exit。
- 使用class内自定义的operator new的set_new_handler可以替换global new-handler。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50class NewHandlerHolder
{
public:
// 取得当前的的new-handler
explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {}
// 释放它
~NewHandlerHolder() {
std::set_new_handler(handler);
}
private:
std::new_handler handler;
// 禁用copying函数
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder& oeprator=(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 currentHandler = 0;
};
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-handler
NewHandlerHolder h(std::set_new_handler(currentHandler));
// 恢复global new-handler
return ::operator new(size);
}
// 函数声明,将在Widget对象分配失败时被调用
void outOfMem();
// 设定outOfMem为Widget的new-hander函数
Widget::set_new_handler(outOfMem);
// 分配失败调用outOfMem
Widget *pw1 = new Widget;
// 分配失败调用global new-handler函数
std::string *ps = new std::string;
// 设定Widget专属的new-handing函数为null
Widget::set_new_handler(0);
// 如果分配失败,立刻抛出异常
Widget *pw2 = new Widget; - 现在设计一个可以被任何有需要的class使用的template: 怪异的循环模板模式(curiously recurring template pattern;CRTP)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28template<typename 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<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>::operator new(std::size_t size) throw(std::bad_alloc)
{
NewHandlerHolder h(std::set_new_handler(currentHandler));
return ::operator new(size);
}
template<typename T>
std::new_handler NewHandlerSupport<T>::currenHandler = 0;
// 对于显式声明nothrow,只能保证operator new不抛出异常,构造函数可能又new一些内存,这就无法强迫了
// Widget *pw = new (std::nothrow)Widget;
条款50:了解new和delete的合理替换时机
- 为什么要替换编译期提供的operator new或operator delete呢?由三个理由:
- 用来检测运用上的错误。如果new出的内存delete失败,会导致内存泄露(memory leaks)。new上多次delete会导致不确定行为。如果我们自行定义operator news,超额分配内存,在额外内存上放置byte patterns(签名,signatures)。这样delete时就可以发现分配区中有哪些点发生了overrun(写入点在分配区尾端之后)或underruns(写入点在分配区起点之前)。这时候就可以log那个事实了。
- 为了强化效能。对于一些需求,包括大块内存、小块内存、大小混合型内存。它们接纳各种分配形态,从程序存活期间的少量区块动态分配,到大量短命对象的持续分配和归还。必须考虑破碎问题(fragmentation)。这会导致程序有总量足够但分散为很多小区块的自由内存,却无法分配大区块内存。
- 为了收集使用上的统计数据。制定news和delete之前,先收集软件如何使用动态内存。分配区块的大小分配?寿命分布?FIFO次序还是LIFO次序或随机分配?不同执行阶段有不同分配/归还形态?任何时刻使用最大动态分配量是多少?
- 计算机体系结构(computer architectures)要求特定的类型必须放在特定内存地址上,例如指针的地址必须是4倍数(for-byte aligned)或doubles地址是8倍数(eight-byte aligned)。齐位(alignment)意义重大,C++要求operator news返回的指针都有适当的内存对齐。malloc就是在这样做的,所以operator new返回一个malloc指针是安全的。然而上面代码中返回一个malloc且偏移一个int大小的指针,没人保证它的安全!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;
// 还有若干错误
void* operator new(std::size_t size) throw(std::bad_alloc)
{
using namespace std;
// 增加大小,使之能够塞入两个signatures
size_t realSize = size + 2 * sizeof(int);
void* pMem = malloc(realSize);
if (!pMem) throw bad_alloc();
// 将signature写入内存的前段落和后段落
*(static_cast<int*>(pMem)) = signature;
*(reinterrpret_cast<int*>(static_cast<Byte*>(pMem) + realSize - sizeof(int))) = signature;
// 返回指针,指向第一个signature之后的内存位置
return static_cast<Byte*>(pMem) + sizeof(int);
} - 摘要:
- 为了检测运用错误。
- 为了收集动态分配内存之使用统计信息
- 为了增加分配和归还速度
- 为了降低缺省内存管理器带来的空间额外开销。
- 为了弥补缺省分配器中的非最佳齐位(suboptimal alignment)。
- 为了将相关对象成簇集中。
- 为了获得非传统的行为。
条款51:编写new和delete时需固守常规
- operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,应该调用new-handler。它也应该有能力处理0 bytes、null指针申请。class专属版本还应该处理”比正确大小更大的(错误)申请“。
条款52:写了placement new也要写placement delete
- 如果operator new接受的参数除了一定会有个size_t之外还有其他,这就是所谓的placement new。众多placement new版本中特别有用的一个是”接受一个指针指向对象该被构造之处“:
1
2
3
4
5
6// 正常的operator new
void* operator new(std::size_t) throw(std::bad_alloc);
// global作用域中的正常签名式
void operator delete(void *rawMemory) throw();
// class作用域中典型的签名式
void operator delete(void *rawMemory, std::size_t size) throw();这个版本在C++ STL中,只要#include <new>就可以用它。1
void* operator new(std::size_t, void *pMemory) throw(); // placement new
举个例子,现在写一个class专属的operator new,要求接受一个ostream,用来log相关分配信息,同时写一个正常形式的class专属operator delete:对于类似的new placement版本,operator delete如果接受额外参数,称为placement deletes。带有额外参数的operator new应该调用带相同额外参数的对应版本operator delete。1
2
3
4
5
6
7
8
9
10
11
12
13class Widget
{
public:
...
// 非正常形式的new
static void* operator new(std::size size, std::ostream &logStream) throw(std::bad_alloc);
// 正常class专属delete
static void operator delete(void *pMemory, std::size_t size) throw();
...
}
// 这里有微妙的内存泄漏
// 调用operator new并传递cerr为其ostream实参,这个动作会在构造函数抛出异常时泄露内存
Widget *pw = new (std::cerr) Widget; - 为了消弭代码中的内存泄露,Widget有必要声明一个placement delete,对应于有志记功能(logging)的placement new: 对于placement delete自动调用期间构造函数可能会抛出异常,代码会自动调用placement delete;如果没有抛出异常,则只会调用普通operator delete。
1
2
3
4
5
6
7
8
9class Widget
{
public:
...
static void* operator new(std::size_t size, std::ostream &logStream) throw(std::bad_alloc);
static void operator delete(void *pMemory) throw();
static void operator delete(void *pMemory, std::ostream &logStream) throw();
...
}
顺带一提,成员函数的名称会掩盖外围作用域中的相同名称,假设有一个base class,其中声明唯一的placement operator new,用户会无法使用正常的new:需要记住的时,C++在global作用域中提供了以下operator new:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Base
{
public:
...
// 会掩盖正常版本
static void* operator new(std::size_t size, std::ostream &logStream) throw(std::bad_alloc);
...
};
class Derived : public Base
{
public:
...
static void* operator new(std::size_t size) throw(std::bad_alloc);
...
};
Base *pb = new Base; // error
Base *pb = new (std::cerr) Base; // ok
Derived *pb = new (std::clog) Derived;// error
Derived *pb = new Derived; // ok除非你要阻止class用户使用这些形式,否则确保它们在你生成的定制型operator new之外还可用。对于每个operator new也有对应的operator delete。如果希望这些函数有平常的行为,令你class专属版本调用global版本:1
2
3
4
5
6// normal new
void* operator new(std::size_t) throw(std::bad_alloc);
// placement new
void* operator new(std::size_t, void *) throw();
// nothrow new
void* operator new(std::size_t, const std::nothrow_t &) throw(); // 491
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class 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 ::opeator 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); }
};
// 供想自定义扩充标准形式的用户,利用继承机制或using声明式(33)取得标准形式
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 oeprator delete(void *pMemory, std::ostream &logStream) throw();
...
};