在C++中,应该经常会用到new、delete,其是C++的一个关键字,也是一个操作符,下面对其了解和学习做一个总结和探讨。
new和delete的过程
要了解C++中的new
和delete
,首先得对在我们使用new和delete的时候,其实际进行了哪些操作。
简单定义一个类:
class A {
int size;
};
当使用关键字new
在堆上动态创建一个对象A时,比如 A* p = new A()
,它通常情况下做了三件事:
- 在堆上申请一块内存空间
- 调用构造函数 (调用A的构造函数,如果有的话)
- 返回该指针
当然,若我们创建的是简单类型的变量,那么第二步会被省略。
当我们delete的时候也是如此,比如我们delete p 的时候,其行为如下:
- 定位到指针p所指向的内存空间,然后根据其类型,调用其自带的析构函数(内置类型不用)
- 然后释放其内存空间(将这块内存空间标志为可用,然后还给操作系统)
下面详细展开说说new操作符。
new
c++中,new是一个重要的关键字,而实际上是由编译器进行展开,如上述说会拆分为2次调用,一次是operator new,另一次就是构造函数。而operator new有三种重载形式:
throwing (1)
void* operator new (std::size_t size) throw (std::bad_alloc);
nothrow (2)
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
placement (3)
void* operator new (std::size_t size, void* ptr) throw();
其中(1)、(2)的区别仅在于是否抛出异常,当分配失败时,前者抛出bad_alloc异常,后者返回null,不会抛出异常。其源码实现为:
__device__ inline void *operator new(__SIZE_TYPE__ size) {
if (size == 0) {
size = 1;
}
return ::malloc(size);
}
而(3)则是placement new,它也是对operator new的一个重载,定义于**#include **中,它多接收一个ptr参数,但它只是简单地返回ptr。其在new.h下的源代码如下:
inline void* operator new (std::size_t, void* __p) _NOEXCEPT {return __p;}
其可以实现在ptr所指地址上构建一个对象(通过调用其构造函数),这在内存池技术上有广泛应用。 它的调用形式为:
new(p) A();
其三种重载形式的简单使用:
#include <iostream>
class A
{
public:
A()
{
std::cout<<"call A constructor"<<std::endl;
}
~A()
{
std::cout<<"call A destructor"<<std::endl;
}
void* operator new(size_t size)
{
std::cout<<"call A::operator new"<<std::endl;
return malloc(size);
}
void* operator new(size_t size, const std::nothrow_t& nothrow_value)
{
std::cout<<"call A::operator new nothrow"<<std::endl;
return malloc(size);
}
};
class B {
public:
B()
{
std::cout<<"call B constructor"<<std::endl;
}
~B()
{
std::cout<<"call B destructor"<<std::endl;
}
};
void* ::operator new(size_t size)
{
std::cout<<"call global operator new"<<std::endl;
return malloc(size);
}
int main()
{
A* p1 = new A;
delete p1;
A* p2 = new(std::nothrow) A;
delete p2;
B* p3 = (B*)operator new(sizeof (B));
new(p3)B();
p3->~B();
delete p3;
B* p4 = new B;
delete p4;
return 0;
}
输出:
call A::operator new
call A constructor
call A destructor
call A::operator new nothrow
call A constructor
call A destructor
call global operator new
call B constructor
call B destructor
call global operator new
call B constructor
call B destructor
new的重载是在命令空间由内向外,寻找实现方法的,只要找到operator new()函数就不再向外查找,如果参数符合则通过,如果参数不符合则报错,而不管全局是否还有相匹配的函数原型。比如如果这里只将A中operator new(size_t, const std::nothrow_t&)删除掉,就会报错:
requires single argument 'size', but 2 arguments were provided
尽量不要重载全局的operator new,如果必需重载,则要把placement new以及数组形式同样进行重载。
operator new运用技巧和相关应用:
1 . operator new重载运用于调试
perator new的重载是可以有自定义参数的,通常的做法是给operator new添加两个参数:char* file, int line,这两个参数记录new关键字的位置,然后再在new时将文件名和行号传入,这样我们就能在分配内存失败时给出提示:文件名和行号。linux下可以使用宏定义获取相关信息,文件名__FILE__
,行号__LINE__
,函数名__FUNCTION__
等等。
//A.h
class A
{
public:
A()
{
std::cout<<"call A constructor"<<std::endl;
}
~A()
{
std::cout<<"call A destructor"<<std::endl;
}
void* operator new(size_t size, const char* file, int line)
{
std::cout<<"call A::operator new on file:"<<file<<" line:"<<line<<std::endl;
return malloc(size);
return NULL;
}
};
//Test.cpp
#include <iostream>
#include "A.h"
#define new new(__FILE__, __LINE__)
int main()
{
A* p1 = new A;
delete p1;
return 0;
}
2 . 内存池优化
operator new的另一个大用处就是内存池优化,内存池的一个常见策略就是分配一次性分配一块大的内存作为内存池(buffer或pool),然后重复利用该内存块,每次分配都从内存池中取出,释放则将内存块放回内存池。在客户端调用的是new关键字,可以改写operator new函数,让它从内存池中取出(当内存池不够时,再从系统堆中一次性分配一块大的),至于构造和析构则在取出的内存上进行,然后再重载operator delete,它将内存块放回内存池。关于内存池和operator new请参考文献《http://www.relisoft.com/book/tech/9new.html c++ operator new重载和内存池技术》
关于new和内存分配的一些其他点
1 . set_new_handler
set_new_handler可以在malloc(需要调用set_new_mode(1))或operator new内存分配失败时指定一个入口函数new_handler,这个函数完成自定义处理(继续尝试分配,抛出异常,或终止程序),如果new_handler返回,那么系统将继续尝试分配内存,如果失败,将继续重复调用它,直到内存分配完毕或new_handler不再返回(抛出异常,终止)。
2 . placement new
在使用placement new时,如果要一次性分配一个对象数组,必要要多申请sizeof(int)大小的内存空间,如下:
void* ptr = malloc(sizeof(std::string)*10 + sizeof(int));
new(ptr)std::string[10];
delete与内存释放
如new一样,delete也大致如此:函数 operator delete对于内建的delete operator(操作符)就好像 operator new 对于new operator一样。
string *ps;
...
delete ps; //使用delete operator.
内存释放动作是由operator delete执行的,通常声明如下:
void operator delete(void* memoryToBeDeallocated);
如此,delete ps,会造成编译器生成大致代码如下:
ps->~string();//调用析造函数
operator delete(ps);//释放对象所占用的内存
但是有一点需要注意,operator delete的自定义参数重载并不能手动调用。比如
void* operator new(size_t size, int x)
{
cout<<" x = "<<x<<endl;
return malloc(size);
}
void operator delete(void* p, int x)
{
cout<<" x = "<<x<<endl;
free(p);
}
如下调用是无法通过的:
A* p = new(3) A;//ok
delete(3) p;//error C2541: “delete”: 不能删除不是指针的对象
事实上以上自定义参数operator delete 只在一种情况下被调用:当new关键字抛出异常时。
如果只打算处理原始的、未设初值的内存,应该完全回避 new
和delete
。改为调用operator new
取得内存并以operator delete
归还系统。
void* buffer=operator new (50*sizeof(char));//分配内存,放置50个char,没有调用构造函数
......
operator delete(buffer); //释放内存,而没有直接调用析构函数。
类似于malloc和free。