- 一条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) type
new (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 );
}