常用的new表达式(new operator)和operator new(size_t sizt)函数的区别

先给结论:

  • new operator 是先调用operator new函数来分配返回值为void*的内存,然后再调用作用类型的构造函数去初始化赋值这块内存。如string * str = new string("hello");就是对new表达式的常用方式。

这里我们可以这么理解,new表达式(new operator)其实可以分解为两部,即先调用new操作符(operator new)申请内存,再调用placement new来初始化对象。即上面对new表达式的使用string * str = new string("hello");等价于下面两句。

void *buffer = ::operator new(sizeof(string));
buffer = new(buffer) string(“hello”);
  • 而operator new函数相当于是malloc,其实它的内部实现也就是调用了void * malloc(size_t size);函数。
 void* operator new(size_t sizt)
{
      return malloc(size);      
}

C++当中3种new的用法

侯捷侯老师的著作《STL源码剖析》,书中第二张开始讲到了stl中的allocator的一个简易实现,发现原来cpp中的new其实除了我们平常用的最多的new表达式(new operator)之外,还有剩下两种用法。

1. new operator/delete operator

  • new operator,也叫new表达式,是我们在cpp中最常见的new的用法,比如:
    string *pStr = new string("abd");
    这个new表达式完成了两件事情:申请内存和调用类的构造函数初始化对象。

  • 与之相对应的是delete operator(delete 表达式),它完成了两件事情:调用所指向元素的析构函数,然后释放内存空间。

2. operator new(operator new[])/operator delete(operator delete[])

operator new(或者operator new[]),其实是标准库函数,容易和new operator搞混。类似与c当中的malloc库函数,只负责申请一块原始的未命名的内存空间,例如:
void *buffer = ::operator new(sizeof(string));

  • operator delete(或者 operator delete[]),是标准库函数,类似于c当中free库函数,负责释放内存空间。

  • 标准库点故意了operator new函数和operator delete函数的8个重载版本(c++ primer 中文版第5版,P727),其中前4个版本可能抛出bad_alloc异常,后4个版本则不会抛出异常:

// 这4个版本可能抛出异常
void *operator new(size_t);         // 分配一个对象
void *operator new[](size_t);           // 分配一个数组
void *operator delete(void*) noexcept;      // 释放一个对象
void *operator delete[](void*) noexcept;    // 释放一个数组
// 这些版本承若不会抛出异常
void *operator new(size_t, nothorw_t &);        // 分配一个对象
void *operator new[](size_t, nothorw_t &);      // 分配一个数组
void *operator delete(void*, nothorw_t &) noexcept; // 释放一个对象
void *operator delete[](void*, nothorw_t &) noexcept;   // 释放一个数组

其中类型nothrow_t是定义在new头文件中的一个struct,在这个类型中不包含任何成员。noexcept异常说明符用来指定其不抛出异常。

  • 当我们自己定义了(函数重载,函数名不变但是参数变)全局的operator new函数和operator delete函数后,编译器会优先使用我们自己定义的operator new和operator delete,这样我们就担负起了控制动态内存分配的职责(比如自己设计的内存池,内部就可以不用malloc来申请内存,甚至可以用全局变量的内存来分配),因此这两个函数必须是正确的。我们也可以定义它们为成员函数,如果同时定义了全局和类的成员函数,优先使用成员函数版本的,如果需要使用全局版本的,可以用作用于运算符::operator new和::operator delete。

3. placement new

  • placement new,也叫定位new表达式,它用于在给定的内存中初始化对象(但不分配内存),例如:
void *buffer = ::operator new(sizeof(string));
buffer = new(buffer) string(“abd”);
  • 与placement new类似,我们可以通过对象调用析构函数,也可以通过对象的指针或引用调用析构函数,这与调用其他成员函数没什么区别:
string *sp = new string("a value"); // 分配并初始化一个string对象
sp->~string();
//调用析构函数会销毁对象,但是不会释放内存。

这里我们可以这么理解,new表达式(new operator)其实可以分解为两部,即先调用new操作符(operator new)申请内存,再调用placement new来初始化对象。

来看一个例子,例子中对3个new的用法都做了举例:

#include       // for placement new
#include    // for string
#include  // for cout
using namespace std;
int main(void)
{
    void* buffer = ::operator new(sizeof(string));  // operator new
    buffer = new(buffer) string("123");             // placement new
    cout << *(string*)buffer << endl;
    // equals new operator
    string* str = new string("123");
    cout << *str << endl;
    delete str;
    ::operator delete(buffer);
    return 0;
}

4. 什么时候需要用到operator new和placement new

  • 如果我们需要把对象的内存分配操作对象的初始化操作分离开来,就需要用到operator new 和placement new。
    其实在stl里面allocator就用到了类似的做法,其中allocate负责向系统申请一块指定大小的内存(operator new),而construct负责在这块内存上根据模板类的类型T来调用T的构造函数初始化内存(placement new)。同理deallocat和destory负责释放内存和析构对象。下面是一个简单的allocator实现,画红线部分即为两种new和两种delete的用法。
    简单的allocate实现

你可能感兴趣的:(常用的new表达式(new operator)和operator new(size_t sizt)函数的区别)