C++ operator new 用法实例讲解
C++中的内存分配可以使用C风格的malloc和free,也可以使用new和delete。我之前曾经转过一篇文章,详细的描述了它们之间的区别。今天把new运算符的三种用法简单描述一下,正好这两天正在用placement new方式实现的一个线程内存池提高程序性能,改天把这块的代码整理一下,放到GitHub上。
new有三种形式:
C++98
(1) void* operator new (std::size_t size) throw (std::bad_alloc); (2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw(); (3) void* operator new (std::size_t size, void* ptr) throw();
C++11
(1) void* operator new (std::size_t size); (2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept; (3) void* operator new (std::size_t size, void* ptr) noexcept;
第一种形式分配size字节的存储空间,如果成功的话返回一个非空指针,指向分配空间第一个字节。如果失败的话,会抛出bad_alloc异常。 第二种形式和第一种一样,差别在于,如果失败的话,不抛出异常,而是返回一个null指针。 第三种形式只是返回ptr指针,并不分配内存空间。这里的ptr应该指向先前已经分配好的空间,这里的new调用对象的构造函数,在ptr指向的内存空间构造对象或对象数组。ptr指向的内存只要不释放,可以重复使用。所以这种用法一般在对象池或内存池实现中使用。
示例代码:
// operator new example #include // std::cout #include // ::operator new struct MyClass { int data[100]; MyClass() {std::cout << "constructed [" << this << "]\n";} }; int main () { std::cout << "1: "; MyClass * p1 = new MyClass; // 调用operator new (sizeof(MyClass))分配内存,然后在新分配的内存空间构造新的对象 std::cout << "2: "; MyClass * p2 = new (std::nothrow) MyClass; // 调用operator new (sizeof(MyClass),std::nothrow)分配内存,然后在新分配的内存空间构造新的对象 std::cout << "3: "; new (p2) MyClass; // 不分配内存 -- 调用operator new (sizeof(MyClass),p2)在p2指向的内存空间构造一个新的对象 // 以下这种方式显示调用operator new运算符,会分配内存,但不调用MyClass的构造函数 std::cout << "4: "; MyClass * p3 = (MyClass*) ::operator new (sizeof(MyClass)); delete p1; delete p2; delete p3; return 0; }
执行结果:
1: constructed [0x7faf5ec02920] 2: constructed [0x7faf5ec02ab0] 3: constructed [0x7faf5ec02ab0] 4:
可以看出,第2次和第3次的对象构造都是在同一块内存空间,即是对同一块内存进行了重复使用。
placement new/delete 主要用途是:反复使用一块较大的动态分配成功的内存来构造不同类型的对象或者它们的数组。例如可以先申请一个足够大的字符数组,然后当需要时在它上面构造不同类型的对象或数组。placement new不用担心内存分配失败,因为它根本不分配内存,它只是调用对象的构造函数。
示例代码:
// operator new example #include // std::cout #include // ::operator new struct MyClass { int data[100]; MyClass() {std::cout << "constructed [" << this << "]\n";} }; int main () { std::cout << "sizeof(MyClass): " << sizeof(MyClass) << std::endl; // 分配2*sizeof(MyClass)字节内存空间 char *pArray = new(std::nothrow) char[sizeof(MyClass) * 2]; char *p = pArray; // 第一个对象从内存开始位置构造 MyClass *p1 = new(p) MyClass; // 移动指针位置,并在新的位置构造第二个对象 p += sizeof(MyClass); MyClass *p2 = new(p) MyClass; // 要显式的调用析构函数 p1->MyClass::~MyClass(); p2->MyClass::~MyClass(); // 最后释放内存空间 delete []pArray; return 0; }
执行结果:
sizeof(MyClass): 400 constructed [0x7fc0dd402920] constructed [0x7fc0dd402ab0]
一个MyClass占用400字节,第一个对象和第二个对象的地址相差正好400字节(0x7fc0dd402ab0 - 0x7fc0dd402920 = 0x190)。
第二个示例中,我的执行结果和第一个示例前两个对象的内存地址是相同的,这个在不同的机器上显示可能会有所不同。