• 一条new表达式的过程:
    • 1、分配内存
      • 调用标准库函数operator new ( operator new[] ) ,该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象(或者对象的数组)。
    • 2、执行构造,初始化内存。
    • 3、对象被分配了空间,构造完成。
  • 一条delete表达式的过程:
    • 1、对象执行析构。
    • 2、调用库函数operator delete(operator delete[])

所以我们说的“重载new和delete ”,实际上是重载operator new函数和operator delete函数,来改变new和delete的行为,我们是无法直接重载new和delete的。

一、查找顺序

执行new/delete时,operator new/delete函数的查找顺序如下:

  1. 类内部,是否有operator new/delete的成员函数,没有找到,下一步。
  2. 全局作用域中查找,没有找到,下一步。
  3. 调用标准库的operator new函数。
  1. Fucker *p = new Fucker; // 按上述顺序查找
  2. Fucker *p = ::new Fucker; // 只查找全局作用域的operator new

二、重载operator new

标准库重载了operator new函数的4个版本,2个抛出异常,2个不抛出异常。
我们自定义的话,必须重载在全局作用域、类作用域中。在类作用域中,默认是静态方法,无需显式声明static。

全局作用域

  1. // ***************************************************************
  2. // 以下版本,当new失败时,会抛出std::bad_alloc异常
  3. // ***************************************************************
  4. // size_t:存储指定类型对象所需的字节数;
  5. void *operator new(size_t size); // T* t = new T时触发。
  6. void *operator new[](size_t); // T* t = new T[10]时触发。
  7. // ***************************************************************
  8. // 以下版本,当new失败时,不会抛出异常
  9. // ***************************************************************
  10. // placement new(重载operator new的一种方式)
  11. void *operator new(size_t, nothrow_t&) noexcept; // T* t = new(nothrow) T时触发。
  12. void *operator new[](size_t, nothrow_t&) noexcept; // T* t = new(nothrow) T[10]时触发。
  13. // nothrow和nothrow_t在new头文件中的定义如下:
  14. // namespace std
  15. // {
  16. // struct nothrow_t { };
  17. // extern nothrow_t const nothrow; // nothrow 是const nothrow_t类型。
  18. // }
  19. // ***************************************************************
  20. // 不可重载版本,这是供内部调用的库函数
  21. // 所有的重载版本最终都是调用这个函数。
  22. // ***************************************************************
  23. void *operator new(size_t, void*);

placement new

重载operator new时,带入了除size以外的参数,这种重载叫做placement new,也叫new的定位new形式。这样做的目的主要是通过传入额外的参数来扩展new的功能。

  1. void* operator new(size_t size, ParamType param); // placement new

使用场景:

  1. // 这是C++内部重载的一个版本,传入一个nothrow_t,则new在发生异常时,也不会抛出异常。
  2. void* operator new(size_t size, nothrow_t& nothrow);
  3. // 这是C++内部的重载版本。
  4. // 传入一个指针类型参数,则new直接在指针所指内存上构造对象,然后直接返回该指针。
  5. // 换句话说new会认为已经分配好了内存,直接在内存当中初始化即可。
  6. // 有点类似allocator的construct,但是要更自由,这个指针可以是任意指针(如不指向动态内存)
  7. // 在allocator出现之前,就是通过placement new来实现内存池的(分配内存与构造对象分离)
  8. void* operator new(size_t size, void* ptr); // 此版本不可自定义,供内部调用。

使用方式如下:

  1. // place_address:任意指针,将在指向的内存上构造对象。
  2. // initializers:初始值列表,用于构建对象。
  3. // type:带构建对象类型。
  4. new (place_address) type
  5. new (place_address) type (initializers)
  6. new (place_address) type [size]
  7. new (place_address) type [size] { braced initializer list }
  8. string *sp = new string(" a value"); // 分配并初始化一个string对象
  9. sp->~string(); // 类似allcator的destroy,销毁对象,注意并没有释放内存。这块内存又可以构建新对象了。
  10. sp = new(sp) string("fuck"); // "fuck"

类作用域

  1. class A {
  2. public:
  3. A() = default;
  4. virtual ~A() = default;
  5. public:
  6. void* operator new(size_t); // 默认就是static类型,无需显式声明
  7. void* operator new[](size_t);
  8. void* operator new(size_t, nothrow_t&) noexcept;
  9. void* operator new[](size_t, nothrow_t&) noexcept;
  10. }

三、重载operator delete

标准库重载了operator new函数的6个版本,4个抛出异常,2个不抛出异常。
注意,在执行operator delete函数之前,已经调用了对象的析构函数,因此不要再在delete重载执行析构,否则将出错,切记切记。

全局作用域

  1. // **********************************************************************
  2. // 以下版本可能抛出异常
  3. // **********************************************************************
  4. void operator delete(void*) noexcept; // delete t时触发
  5. void operator delete[](void*) noexcept; // delete[] t时触发
  6. void operator delete(void* ptr, std::size_t sz) noexcept; // sz: ptr指向内存的字节数
  7. void operator delete[](void* ptr, std::size_t sz) noexcept;
  8. // **********************************************************************
  9. // 以下版本不会抛出异常
  10. // **********************************************************************
  11. void operator delete(void*, nothrow_t&) noexcept;
  12. void operator delete[](void*, nothrow_t&) noexcept;
  13. // nothrow和nothrow_t在new头文件中的定义如下:
  14. // namespace std
  15. // {
  16. // struct nothrow_t { };
  17. //
  18. // extern nothrow_t const nothrow; // nothrow 是const nothrow_t类型。
  19. // }

类作用域

在类内部重载operator delete同理operator new。

四、注意

没有死到临头,就不要重载operator new/delete,因为这将变得非常难以处理,可移植性、地址对齐、线程安全等等,重载一个operator new是非常考究的。

在C++内部已经有了多个operator new/delete的重载版本,这些版本最好不要再覆盖(如nothrow版本)。

当new一个0大小的内存,new也必须返回一个合法指针(非空),内部可以处理分配1 byte内存。

当在基类内部重载operator new时,这是为这个类量身打造的,“大小刚刚好”,因此不要在其派生类当中使用,各用个的。在基类中可如下重载operator new

  1. void* Base::operator new(size_t size){
  2. if(size == sizeof(Base)) return ::operator new(size);
  3. }

若类将大小有误的分配行为转交::operator new执行,则必须将大小有误的删除行为转交::operator delete执行。

  1. void Base::operator delete(void *ptr, std::size_t size)
  2. {
  3. if(!ptr)return;
  4. if( size!=sizeof(Base) )
  5. {
  6. ::operator delete(ptr);
  7. return;
  8. }
  9. // 归还rawMemory所指的内存;
  10. // 特殊情况:如果要删除的对象派生自某个base class而后者欠缺virtual析构函数,则这个size的值将不准确。
  11. return;
  12. }

最好不要重载operator delete,用默认的即可。因为重载operator delete的情况比较复杂:

  1. // 假设重载了以下函数:
  2. void* operator new(size_t size, int fuck) {} // 自定义的重载版本。
  3. void operator delete(void* ptr){} // 这是覆盖了内部重载的版本
  4. void operator delete(void* ptr, int fuck) {} // 与重载的operator new版本对应
  5. int main(){
  6. try{
  7. void* bitch = new(1) int; // 假设构造函数中抛出异常,则会调用operator delete的fuck版本。
  8. // 以保证即使出现异常,依然能释放分配的内存。见下面例子。
  9. delete bitch; // 若不抛出异常,则走这一步正常删除。
  10. // 若没有自定义覆盖一个内部重载版本的operator delete,则这里包语法错误。
  11. // 找不到合适的delete。
  12. }
  13. catch(const std::exception&){}
  14. }

五、代码实践

test.h

  1. class New1
  2. {
  3. public:
  4. New1() = default;
  5. virtual ~New1() = default;
  6. void* operator new( size_t size ) { return std::malloc( size ); }
  7. void operator delete( void* ptr ) noexcept { std::free( ptr ); }
  8. };
  9. class New2
  10. {
  11. public:
  12. New2() = default;
  13. virtual ~New2() = default;
  14. void* operator new( size_t size ) { return ::operator new( size ); }
  15. void* operator new[]( size_t size ) { return ::operator new( size ); }
  16. void operator delete( void* ptr, size_t size );
  17. void operator delete[]( void* ptr, size_t size );
  18. };
  19. class New3
  20. {
  21. public:
  22. New3();
  23. virtual ~New3() = default;
  24. void* operator new( size_t size, bool b );
  25. void operator delete( void* ptr, bool b );
  26. void operator delete( void* ptr, size_t size ) { return ::operator delete( ptr ); };
  27. };

test.cpp

  1. void main()
  2. {
  3. New1* new1 = new New1;
  4. New2* new2 = new New2;
  5. New2* new21 = new New2[10];
  6. delete new1;
  7. delete new2;
  8. delete[] new21;
  9. // 当New3构造函数抛出异常时,调用我们自定义的与new(true)配套的operator delete来释放内存
  10. // 当New3构造函数不抛出异常,则走delete new3正常释放内存。
  11. try
  12. {
  13. New3* new3 = new( true ) New3;
  14. delete new3;
  15. }
  16. catch( const std::exception & ) { }
  17. return;
  18. }
  19. void New2::operator delete( void* ptr, size_t size )
  20. {
  21. std::cout << "New2::operator new size: " << size << endl;
  22. return ::operator delete( ptr );
  23. }
  24. void New2::operator delete[]( void* ptr, size_t size )
  25. {
  26. std::cout << "New2::operator new[] size: " << size << endl;
  27. return ::operator delete( ptr );
  28. }
  29. New3::New3()
  30. {
  31. throw std::runtime_error( "runtime error." );
  32. }
  33. void* New3::operator new( size_t size, bool b )
  34. {
  35. if( !b ) return nullptr;
  36. return ::operator new( size );
  37. }
  38. void New3::operator delete( void* ptr, bool b )
  39. {
  40. if( b ) ::operator delete( ptr );
  41. }