条款49:了解new-handler的行为
- 当operator new或者operator new[]分配内存时,如果不能获得指定大小的内存时,则会抛出异常,即std::bad_alloc异常。
- 可以设置系统的new_handler函数,这样当operator new或者operator new[]不能获取指定大小的内存时,则会调用此函数,但是如果用户不在函数中将std::new_handler设置为空的话,则会一直调用此用户函数,如下:
#include <iostream>
void OutOfMem()
{
std::cout << "Entering OutOfMem Function" << std::endl;
}
int main()
{
std::set_new_handler(OutOfMem);
int *p = new int[10000000000000000L];
delete [] p;
return 0;
}
上面这段代码中,OutOfMem函数会被一直调用,所以系统提供这个接口的本意是希望用户在实现这个函数时回收内存。除非修改为以下代码,则会抛出异常:
#include <iostream>
void OutOfMem()
{
std::cout << "Entering OutOfMem Function" << std::endl;
std::set_new_handler(nullptr); //重新将new_handler设置为空!
}
int main()
{
std::set_new_handler(OutOfMem);
int *p = new int[10000000000000000L];
delete [] p;
return 0;
}
- 可以在类中提供一个专用的new_handler,方法是在其operator new实现函数中设置,如下:
#include <iostream>
void OutOfMem_For_MyClass()
{
std::cout << "Entering OutOfMem_For_MyClass" << std::endl;
};
class MyClass {
public:
void* operator new(std::size_t size)
{
std::cout << "Entering MyClass::operator new" << std::endl;
std::set_new_handler(OutOfMem_For_MyClass);
auto res = ::operator new(size);
std::set_new_handler(nullptr);
return res;
}
};
int main()
{
MyClass* p = new MyClass();
delete p;
return 0;
}
条款50:了解New和Delete的合理替换时机
-
系统在全局域空间内实现了默认的operator new和operator delete,用户可以定义自己的operator new和delete,编译器在搜索时遵循的是从内往外寻找,如下所示:
所以,如果你在自己定义的类里定义了一个operator new,则编译器是不会看到全局域空间内的operator new的(默认),如下:
#include <iostream>
class A {
public:
void* operator new(std::size_t size) {
std::cout << "Entering A::operator new" << std::endl;
return ::operator new(size);
}
};
int main()
{
A* p = new A();
delete p;
return 0;
}
值得注意的是,系统的默认的全局域operator new的定义形式为:
void* operator new(std::size_t size);
但是你也可以定义自己的operator new函数形式,但是第一个参数必须是std::size_t,如下:
#include <iostream>
class A {
public:
void* operator new(std::size_t size, std::string str) {
std::cout << "Entering A::operator new, str = " << str <<std::endl;
return ::operator new(size);
}
};
int main()
{
A* p = new("TEST") A(); // 调用的是自己定义的operator new函数
A* q = new A(); // 编译不通过,因为在子类域中定义了operator new(std::size_t, std::string)函数,它屏蔽了全局域中的operator new(std::size_t)函数。
delete p;
return 0;
}
替换系统默认的operator new和delete有以下应用场景:
- 为了检测运用错误
- 为了收集动态内存使用信息
- 提升性能:增加分配和归还的速度
- 降低缺省内存管理器带来的额外空间开销
- 为了弥补缺省分配器中的非最佳对齐
- 为了将相关对象成簇集中
- 为了获得非传统行为
条款51:编写New和Delete时需固守常规
- operator new的标准写法:
class MyClass {
public:
void* operator new(std::size_t size) {
// 处理size为0的情况
if (size == 0) {
size = 1;
}
// 处理不是MyClass类对象的情况,因为有可能它被继承
if (size != sizeof(MyClass)) {
return ::operator new(size);
}
// 包含一个无限循环
while (true) {
尝试分配size个字节
if (分配成功) {
return 指针
}
// 尝试调用用户设置的new_handler,希望它能帮忙解决内存不足的问题
std::new_handler globalHandler = std::set_new_handler(nullptr);
std::set_new_handler(globalHandler);
if (*globalHandler) (*globalHandler)();
else std::throw std::bad_alloc();
}
}
};
- operator delete的标准写法:
class MyClass {
public:
void operator delete(void* p, std::size_t size) {
if (p == nullptr) return;
if (size != sizeof(MyClass)) {
::operator delete(p, size);
return;
}
归还p指向的内存
return;
}
};
条款52:写了Placement New也要写Placement Delete
- 当你定制了一个placement operator new时,必须写一个对应的placement operator delete,这是因为当调用placement operator new获得内存后,还需要执行对象的构造函数,如果在构造函数中出现异常,则需要提供一个对应的placement operator delete,在这个operator中回收内存,否则会导致内存泄露。
注意:如果你没有定制placement operator new,即使用默认的placement operator new,则上述情况会在默认的placement operator delete处理好。 - 当你在一个类中定义了一个placement operator new时,也屏蔽了默认的全局域的placement operator new,如下:
#include <iostream>
class A {
public:
// 定义一个定制的placement operator new
void* operator new(std::size_t size, void *p) {
return p;
}
};
int main()
{
char* buf = new char[sizeof(A)];
A* p1 = new(buf) A();
A* p2 = new A(); //编译不通过,由于定义了一个定制的placement operator new,所以看不到全局域中的operator new了
delete [] buf;
return 0;
}
所以这时候要在类中定义对应的operator new,如下所示:
#include <iostream>
class A {
public:
// 定义一个定制的placement operator new
void* operator new(std::size_t size, void *p) {
return p;
}
};
int main()
{
char* buf = new char[sizeof(A)];
A* p1 = new(buf) A();
A* p2 = new A(); //编译不通过,由于定义了一个定制的placement operator new,所以看不到全局域中的operator new了
delete [] buf;
return 0;
}
注意:当你定义了placement operator delete时,它只会在使用placement operator new一个对象,并且对象构造函数出现异常时才会被调用,而一般写delete该对象指针时,它只会调用默认的operator delete。如下所示:
#include <iostream>
class A {
public:
// 定义一个定制的placement operator new
void* operator new(std::size_t size, void *p) {
return p;
}
// 定义对应的placement operator delete
void operator delete(void* p, void* pMem) {
return;
}
// 这里假设用户只能使用placement operator new进行分配对象
void operator delete(void *p, std::size_t size) {
// 本示例中啥也不用做
std::cout << "Entering operator delete(void*, std::size_t)" << std::endl;
return;
}
};
int main()
{
char* buf = new char[sizeof(A)];
A* p = new(buf) A();
delete p; // 它只会调用默认的operator delete,所以要实现它,并且要特殊处理
// 在这里释放placement operator new用到的内存
delete []buf;
return 0;
}