- 一条new表达式的过程:
- 1、分配内存
- 调用标准库函数operator new ( operator new[] ) ,该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象(或者对象的数组)。
- 2、执行构造,初始化内存。
- 3、对象被分配了空间,构造完成。
- 1、分配内存
- 一条delete表达式的过程:
- 1、对象执行析构。
- 2、调用库函数operator delete(operator delete[])
所以我们说的“重载new和delete ”,实际上是重载operator new函数和operator delete函数,来改变new和delete的行为,我们是无法直接重载new和delete的。
一、查找顺序
执行new/delete时,operator new/delete函数的查找顺序如下:
- 类内部,是否有operator new/delete的成员函数,没有找到,下一步。
- 全局作用域中查找,没有找到,下一步。
- 调用标准库的operator new函数。
Fucker *p = new Fucker; // 按上述顺序查找Fucker *p = ::new Fucker; // 只查找全局作用域的operator new
二、重载operator new
标准库重载了operator new函数的4个版本,2个抛出异常,2个不抛出异常。
我们自定义的话,必须重载在全局作用域、类作用域中。在类作用域中,默认是静态方法,无需显式声明static。
全局作用域
// ***************************************************************// 以下版本,当new失败时,会抛出std::bad_alloc异常// ***************************************************************// size_t:存储指定类型对象所需的字节数;void *operator new(size_t size); // T* t = new T时触发。void *operator new[](size_t); // T* t = new T[10]时触发。// ***************************************************************// 以下版本,当new失败时,不会抛出异常// ***************************************************************// placement new(重载operator new的一种方式)void *operator new(size_t, nothrow_t&) noexcept; // T* t = new(nothrow) T时触发。void *operator new[](size_t, nothrow_t&) noexcept; // T* t = new(nothrow) T[10]时触发。// nothrow和nothrow_t在new头文件中的定义如下:// namespace std// {// struct nothrow_t { };// extern nothrow_t const nothrow; // nothrow 是const nothrow_t类型。// }// ***************************************************************// 不可重载版本,这是供内部调用的库函数// 所有的重载版本最终都是调用这个函数。// ***************************************************************void *operator new(size_t, void*);
placement new
重载operator new时,带入了除size以外的参数,这种重载叫做placement new,也叫new的定位new形式。这样做的目的主要是通过传入额外的参数来扩展new的功能。
void* operator new(size_t size, ParamType param); // placement new
使用场景:
// 这是C++内部重载的一个版本,传入一个nothrow_t,则new在发生异常时,也不会抛出异常。void* operator new(size_t size, nothrow_t& nothrow);// 这是C++内部的重载版本。// 传入一个指针类型参数,则new直接在指针所指内存上构造对象,然后直接返回该指针。// 换句话说new会认为已经分配好了内存,直接在内存当中初始化即可。// 有点类似allocator的construct,但是要更自由,这个指针可以是任意指针(如不指向动态内存)// 在allocator出现之前,就是通过placement new来实现内存池的(分配内存与构造对象分离)void* operator new(size_t size, void* ptr); // 此版本不可自定义,供内部调用。
使用方式如下:
// place_address:任意指针,将在指向的内存上构造对象。// initializers:初始值列表,用于构建对象。// type:带构建对象类型。new (place_address) typenew (place_address) type (initializers)new (place_address) type [size]new (place_address) type [size] { braced initializer list }string *sp = new string(" a value"); // 分配并初始化一个string对象sp->~string(); // 类似allcator的destroy,销毁对象,注意并没有释放内存。这块内存又可以构建新对象了。sp = new(sp) string("fuck"); // "fuck"
类作用域
class A {public:A() = default;virtual ~A() = default;public:void* operator new(size_t); // 默认就是static类型,无需显式声明void* operator new[](size_t);void* operator new(size_t, nothrow_t&) noexcept;void* operator new[](size_t, nothrow_t&) noexcept;}
三、重载operator delete
标准库重载了operator new函数的6个版本,4个抛出异常,2个不抛出异常。
注意,在执行operator delete函数之前,已经调用了对象的析构函数,因此不要再在delete重载执行析构,否则将出错,切记切记。
全局作用域
// **********************************************************************// 以下版本可能抛出异常// **********************************************************************void operator delete(void*) noexcept; // delete t时触发void operator delete[](void*) noexcept; // delete[] t时触发void operator delete(void* ptr, std::size_t sz) noexcept; // sz: ptr指向内存的字节数void operator delete[](void* ptr, std::size_t sz) noexcept;// **********************************************************************// 以下版本不会抛出异常// **********************************************************************void operator delete(void*, nothrow_t&) noexcept;void operator delete[](void*, nothrow_t&) noexcept;// nothrow和nothrow_t在new头文件中的定义如下:// namespace std// {// struct nothrow_t { };//// extern nothrow_t const nothrow; // nothrow 是const nothrow_t类型。// }
类作用域
在类内部重载operator delete同理operator new。
四、注意
没有死到临头,就不要重载operator new/delete,因为这将变得非常难以处理,可移植性、地址对齐、线程安全等等,重载一个operator new是非常考究的。
在C++内部已经有了多个operator new/delete的重载版本,这些版本最好不要再覆盖(如nothrow版本)。
当new一个0大小的内存,new也必须返回一个合法指针(非空),内部可以处理分配1 byte内存。
当在基类内部重载operator new时,这是为这个类量身打造的,“大小刚刚好”,因此不要在其派生类当中使用,各用个的。在基类中可如下重载operator new
void* Base::operator new(size_t size){if(size == sizeof(Base)) return ::operator new(size);}
若类将大小有误的分配行为转交::operator new执行,则必须将大小有误的删除行为转交::operator delete执行。
void Base::operator delete(void *ptr, std::size_t size){if(!ptr)return;if( size!=sizeof(Base) ){::operator delete(ptr);return;}// 归还rawMemory所指的内存;// 特殊情况:如果要删除的对象派生自某个base class而后者欠缺virtual析构函数,则这个size的值将不准确。return;}
最好不要重载operator delete,用默认的即可。因为重载operator delete的情况比较复杂:
// 假设重载了以下函数:void* operator new(size_t size, int fuck) {} // 自定义的重载版本。void operator delete(void* ptr){} // 这是覆盖了内部重载的版本void operator delete(void* ptr, int fuck) {} // 与重载的operator new版本对应int main(){try{void* bitch = new(1) int; // 假设构造函数中抛出异常,则会调用operator delete的fuck版本。// 以保证即使出现异常,依然能释放分配的内存。见下面例子。delete bitch; // 若不抛出异常,则走这一步正常删除。// 若没有自定义覆盖一个内部重载版本的operator delete,则这里包语法错误。// 找不到合适的delete。}catch(const std::exception&){}}
五、代码实践
test.h
class New1{public:New1() = default;virtual ~New1() = default;void* operator new( size_t size ) { return std::malloc( size ); }void operator delete( void* ptr ) noexcept { std::free( ptr ); }};class New2{public:New2() = default;virtual ~New2() = default;void* operator new( size_t size ) { return ::operator new( size ); }void* operator new[]( size_t size ) { return ::operator new( size ); }void operator delete( void* ptr, size_t size );void operator delete[]( void* ptr, size_t size );};class New3{public:New3();virtual ~New3() = default;void* operator new( size_t size, bool b );void operator delete( void* ptr, bool b );void operator delete( void* ptr, size_t size ) { return ::operator delete( ptr ); };};
test.cpp
void main(){New1* new1 = new New1;New2* new2 = new New2;New2* new21 = new New2[10];delete new1;delete new2;delete[] new21;// 当New3构造函数抛出异常时,调用我们自定义的与new(true)配套的operator delete来释放内存// 当New3构造函数不抛出异常,则走delete new3正常释放内存。try{New3* new3 = new( true ) New3;delete new3;}catch( const std::exception & ) { }return;}void New2::operator delete( void* ptr, size_t size ){std::cout << "New2::operator new size: " << size << endl;return ::operator delete( ptr );}void New2::operator delete[]( void* ptr, size_t size ){std::cout << "New2::operator new[] size: " << size << endl;return ::operator delete( ptr );}New3::New3(){throw std::runtime_error( "runtime error." );}void* New3::operator new( size_t size, bool b ){if( !b ) return nullptr;return ::operator new( size );}void New3::operator delete( void* ptr, bool b ){if( b ) ::operator delete( ptr );}
