new 和 delete 是C++ 中一对动态申请内存的操作符。
new_handler 行为
在std的命名空间中,有这样的代码:
namespace std
{
typedef void (*) () new_handler;
new_handler set_new_handler(new_handler p) throw();
}
set_new_handler
的作用是,允许用户设置当前operator new 失败时的函数,就是new_handler
。
它会返回之前的new_handler
。
很一般的使用如下:
void out_of_memory()
{
cerr<<"out !"<<endl;
}
int main()
{
set_new_handler(out_of_memory);
int* p = new int[100000000000000L];
}
有时候,我们希望对于一些class ,当new 内存失败时能够有它独立的处理函数,那么基于set_new_handler
的一个使用可以如下:
class Some_class
{
static new_handler current_handler; //当前类的new_handler
int c[1000000000000L];
public:
Some_class(){}
static new_handler set_new_handler(new_handler inn)
{
new_handler old(current_handler);
current_handler = inn;
return old;
}
static void* operator new (size_t size)
{
new_handler old = ::set_new_handler(current_handler);
void* p = ::operator new(size);
set_new_handler(old); //需要恢复全局的new_handler
return p;
}
};
new_handler Some_class::current_handler = nullptr;
在以前的博文中,有提到利用类来管理资源。
这里其实也可以运用一样的手法,来自动设置和恢复new_handler
。
于是,再更改一下,就变成这样了:
class New_handler_holder
{
new_handler old_handler;
//防止copy
New_handler_holder(const New_handler_holder&);
New_handler_holder& operator = (const New_handler_holder&);
public:
New_handler_holder(new_handler inn)
{
old_handler = set_new_handler(inn);
}
~New_handler_holder()
{
set_new_handler(old_handler);
}
};
class Some_class
{
static new_handler current_handler;
int c[1000000000000L];
public:
Some_class(){}
static new_handler set_new_handler(new_handler inn)
{
new_handler old(current_handler);
current_handler = inn;
return old;
}
static void* operator new (size_t size)
{
New_handler_holder h(current_handler); //这里这样运用
return ::operator new(size);
}
};
new_handler Some_class::current_handler = nullptr;
如果每次类需要自己的new_handler 的时候,都要编写一段几乎相同的代码。
嗯~于是,我们可以利用模板的技术,帮我们完成代码的复用。
先声明一个类,用来包含new_handler:
template<typename T>
class New_handler_support
{
static new_handler current_handler;
public:
static new_handler set_new_handler(new_handler inn)
{
new_handler old(inn);
current_handler = inn;
return old;
}
static void* operator new (size_t size)
{
New_handler_holder h(current_handler);
return ::operator new(size);
}
};
template<typename T>
new_handler New_handler_support<T>::current_handler = nullptr;
然后使用的时候,让需要有自己new_handler 的类继承它就可以了:
//虽然是public 继承,但不是is-a 关系!!
class Some_class:public New_handler_support<Some_class>
{
int c[1000000000000L];
public:
Some_class(){}
//不需要重新编写operator new 函数
};
编写new 和 delete 时的规则
在global 的作用域中,存在着关于new 和 delete 的函数。
但C++ 提供了操作符重载的机制,所以,我们也可以自己编写 new 和 delete 函数。
至于为什么需要自己编写和什么时候适合自己编写 new 和 delete 函数,在[1] 中的条款50 中有说明。
首先,从operator new 开始。
我们知道,在正常的情况下,如果new 一个内存失败的时候,在抛出一个bad_alloc
异常之前,会循环调用new_handler 的函数,直到内存分配成功。
它也要能够处理分配内存为0 时的情况。C++ 规定,即使客户要求0 bytes,operator new 也得返回一个合理的指针[1]。那么在处理这个情况时,可以返回指向1 个byte 的指针。
所以,operator new 应该遵守以下的规则[1]:
- 应该包含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler.
- 应该能够处理0 bytes 的申请。
于是,我们可以这么写一段简单的实例代码:
//这是分配内存的行为,为了方便,直接使用malloc
//但是实际开发中,这部分可能与自己项目的内存管理相关
void* get_memory(size_t size)
{
if(size < 1000)
return malloc(size);
else return nullptr;
}
//自己编写的new
void* operator new (size_t size) throw(bad_alloc)
{
if(size == 0)
size = 1; //处理0 bytes 的处理
while(true)
{
void* p = get_memory(size);
if(p!=nullptr)
return p;
//为了过去全局的handler
//但是不具备线程安全性
new_handler global_handler = set_new_handler(0);
set_new_handler(global_handler);
if(global_handler) global_handler();
else throw bad_alloc();
}
};
对于delete ,它应该能够处理空指针的delete ,于是:
void delete_memory(void* ptr)
{
free(ptr);
}
//自己编写的delete
void operator delete (void* ptr) throw()
{
if(ptr == nullptr) return; //如果是空指针,那么直接返回
delete_memory(ptr);
}
这是一般的new 和delete。
现在我们探讨一下class 的new 和delete。
class Base
{
public:
static void* operator new (size_t size)
{
//...
}
static void operator delete (void* ptr, size_t size ) throw()
{
//...
}
};
现在,如果有class 继承了Base,那么它也会继承Base 的new。
class Derived:public Base
//...
Derived* pd = new Derived; //调用了base版本的new
这时候如果new 了Derived class,它会调用base 版本的new ,这明显不是我们想要的。
所以,class 专属版本的new 还应该能够处理大小和自身不同的内存申请要求。[1]
class Base
{
public:
static void* operator new (size_t size)
{
if(size != sizeof(Base))
::operator new(size); //如果大小不等于自身,交给全局的new
else
return get_memory(size);
}
static void operator delete (void* ptr, size_t size ) throw()
{
if(ptr== nullptr) return;
if(size != sizeof(Base)) return ::operator delete(ptr);
delete_memory(ptr);
}
};
当然这里面的情况更加复杂一点,如果Base 和Derived 的sizeof 大小是一样的呢?
如果从不会成为基类的类的new,就可以不用检验与自身大小不一致的new ?
我觉得,最好的解决方案是视自己的需求而定,没有最好的技术方案,只有最合适的技术方案。
placement new 与 placement delete
关于placement new 和placement delete 的若干术语[1]:
placement new: 如果operator new 接受的参数除了一定会有的那么size_t 之外,还有其他,这就是所谓的placement new
placement delete : 如果operator delete 接受额外的参数,便称为placement delete
当new 一个对象的时候:
Some_class* ps = new Some_class;
这一过程如下:
- 分配内存
- 调用Some_class 的构造函数
- 返回指针值
如果在调用Some_class 的构造函数中,抛出异常怎么办?
在这个时候,我们是没有办法显示地去回收内存。那这个职责就落到了运行期系统身上。运行期系统会调用与operate new 相应的operator delete 回收内存。
那如果运行期系统找不到相应的delete 函数呢?它两手一摊,表示我无能为力。那这个时候,就发生臭名昭彰的内存泄露了。
就正常的operator new 函数,原型如下[1]:
void* operator new (size_t size) throw(bad_alloc);
对应的operator delete 如下:
void operator delete(void* ptr) throw(); //global 中正常的签名式
void operator delete(void* ptr, size_t size) throw();//class 作用域中典型的签名式
那如果new 的时候调用了placement new 函数,就placement delete 函数就要存在,否则在应对new 抛出异常的情况下,就无法回收内存了。
假设有个类,它重载operator new ,并额外接受一个参数用来输出一些信息,像这样:
class Some_class
{
public:
static void* operator new (size_t s, ostream& out) throw(bad_alloc)
{
out<<" Some_class is new "<<endl;
return malloc(s);
}
};
其中的new 函数就是placement new。
那它也要有相对应的placement delete函数:
static void operator delete (void* ptr, ostream& out) throw();
那现在这个类的定义如下:
class Some_class
{
public:
static void* operator new (size_t s, ostream& out) throw(bad_alloc)
{
out<<" Some_class is new "<<endl;
return malloc(s);
}
static void operator delete (void* ptr, ostream& out) throw()
{
out<<" Some_class is delete "<<endl;
return free(ptr);
}
};
一切都很好,现在我们来试着使用一下这个类:
SomeClass* ps = new (cout) Some_class;//ok
delete ps;//error
然后,发现,new 是正常的,但是delete 的时候却报错了。
那是因为placement delete 只有在“伴随placement new 调用而出发的构造函数出现异常时”才会被调用。[1]
为了理解,不妨再看一遍加粗的字。
也就是说,把placement delete 写出来并不是为了我们自己调用,而是为了解决“伴随placement new 调用而出发的构造函数出现异常时”回收内存的问题。它只是我们编写过程中的一个安全保证。
那么,delete 一个指针的时候,调用的是正常的delete函数。但是上面的类的new 和 delete 声明,掩盖掉了global 作用域下的new 和 delete 函数,所以,我们需要重新声明:
class Some_class
{
public:
static void* operator new (size_t s, ostream& out) throw(bad_alloc)
{
out<<" Some_class is new "<<endl;
return malloc(s);
}
static void operator delete (void* ptr, ostream& out) throw()
{
out<<" Some_class is delete "<<endl;
return free(ptr);
}
static void operator delete (void* ptr) throw()
{
cout<<" ok ,now is normal delete!"<<endl;
return free(ptr);
}
};
事实上,正常的new 也被掩盖了。也就是说,如果我们这个使用代码:
Some_class * ps = new Some_class;
也会报错。
为了解决class 版本下,需要额外定义使用placement new 和delete ,我们可以声明一个基类,然后让别的类继承它,使用using 使得函数可见,就ok了:
class Stander_new_delete_forms
{
public:
// normal new delete
static void* operator new (size_t size) throw(bad_alloc)
{ return ::operator new(size); }
static void operator delete (void* ptr) throw()
{ return ::operator delete(ptr); }
//placement new delete
static void* operator new (size_t size, void* ptr) throw()
{ return ::operator new(size,ptr); }
static void operator delete (void* p_memory, void* ptr) throw()
{ return ::operator delete(p_memory, ptr); }
//nothrow new delete
static void* operator new(size_t size, const nothrow_t& nt) throw()
{ return ::operator new(size, nt); }
static void operator delete(void* ptr, const nothrow_t& nt) throw()
{ return ::operator delete(ptr, nt); }
};
class Some_class: public Stander_new_delete_forms
{
public:
using Stander_new_delete_forms::operator new; //使用using
using Stander_new_delete_forms::operator delete;
static void* operator new (size_t s, ostream& out) throw(bad_alloc)
{
out<<" Some_class is new "<<endl;
return malloc(s);
}
static void operator delete (void* ptr, ostream& out) throw()
{
out<<" Some_class is delete "<<endl;
return free(ptr);
}
};
global 作用域下的new 和 delete
global 作用域下提供了三对的new 和 delete。
void* operator new (size_t size) throw(bad_alloc)
void* operator new (size_t size, void* ptr) throw()
void* operator new(size_t size, const nothrow_t& nt) throw()
void operator delete (void* ptr) throw()
void operator delete (void* p_memory, void* ptr) throw()
void operator delete(void* ptr,const nothrow_t& nt) throw()
分别对应normal new、placement new 和 nothrow new。
normal new 就不提了,经常使用的就是它。
placement new 的是用于指向一个对象该被构造之处。
一个对象的内存通常是固定的,这个版本的作用就是在一个已经开辟内存的对象上,重新构造对象。
有点像在废旧的城池上重新建立江山的意思。使用如下:
Some_class* ps = new Some_class;
ps->~Some_class(); //废旧的城池
void* pv = ps;
ps = new (pv) Some_class; //重新利用
delete ps;
nothrow 的意思就是new 过程中不抛出异常。如果内存分配不成功,那么返回空指针。
Some_class* pns = new (nothrow) Some_class;
if(pns != nullptr)
//...
[参考资料]
[1] Scott Meyers 著, 侯捷译. Effective C++ 中文版: 改善程序技术与设计思维的 55 个有效做法[M]. 电子工业出版社, 2011.
(条款49:了解new_handler 行为;
条款51:编写new 和 delete 时需固守常规;
条款52:写了placement new 也要写placement delete)