一、new的浅析
在C++中,new主要由三种形式:new operator、operator new和placement new
• new operator
new operator即一些C++书籍中(如《C++ Primer》)所说的new表达式(new expression) ,也是我们在C++中用来进行动态内存空间开辟的主要工具。
语法:
语法1:类型* 指针名=new 类型 //在内存的堆区动态开辟一个变量大小的空间 语法2:类型* 指针名=new 类型(初始化值) //在内存的堆区动态开辟一个变量大小的空间,并对该动态变量进行初始化 语法3:类型*指针名=new 类型[数组大小] //在内存的堆区动态开辟一个数组大小的空间
示例:
1 int *value=new int; //语法1示例 2 string *name=new string("Tomwenxing"); //语法2示例 3 double *array=new int[5]; //语法3示例
事实上,当我们使用new来开在内存中动态开辟空间时,new operator主要完成了三项工作:
1.在内存的堆区中动态开辟空间(由operator new来完成,很多编译其借助C语言中的malloc来实现)
2.调用构造函数(C语言中的malloc只开辟空间而不调用构造函数,这也是为什么new可以进行初始化而malloc不行的原因)
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 class Student{ 5 public: 6 Student(){ 7 cout<<"调用默认构造函数"<<endl; 8 } 9 Student(string name):Name(name){ 10 cout<<"调用带参数的构造函数"<<endl; 11 } 12 private: 13 string Name; 14 }; 15 16 int main(){ 17 Student *stu1=new Student; 18 Student *stu2=new Student("Tomwenxing"); 19 delete stu1; 20 delete stu2; 21 return 0; 22 }
3.返回分配的指针(C语言的malloc只返回void*指针,而new返回的指针都有特定的类型而非空指针)
特别注意:new operator=operator new+Constructor(构造函数)
• operator new
new operator的第一步在内存的堆区动态开辟内存实际上就是通过operator new来完成的。通常情况下,new operator的声明如下:
void* operator new(size_t size);
特别注意:
1.operator new的返回值类型是void* ,表示operator new的返回值是一个指向一块原始的、未设置初始值的内存。换句话说,operator new的唯一任务就是负责内存分配;而获取operator new返回的内存并将之转换为一个对象则是new operator的责任。
2.operator new中的size_t参数表示的是需要分配的内存的大小;我们可以将operator new进行重载,甚至添加额外的参数,但operator new的第一个参数的类型必须总是size_t [注]:operator new的size_t参数的大小一般由系统根据实际类型调用sizeof计算得来
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 class Student{ 5 public: 6 Student(){ 7 cout<<"调用默认构造函数"<<endl; 8 } 9 Student(string name):Name(name){ 10 cout<<"调用带参数的构造函数"<<endl; 11 } 12 void* operator new(size_t size){ //对operator new进行了重载 13 cout<<"调用了operator new"<<endl; 14 return malloc(size); 15 } 16 private: 17 string Name; 18 }; 19 20 int main(){ 21 Student *stu1=new Student; 22 cout<<"---------分界线-----------------"<<endl; 23 Student *stu2=new Student("Tomwenxing"); 24 delete stu1; 25 delete stu2; 26 return 0; 27 }
3.利用new创建动态数组时无法对数组中的元素显式初始化(也就是说只能调用默认构造函数,而不能调用带参数的构造函数),并且可以对new[]进行单独的重载。
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 class Student{ 5 public: 6 Student(){ 7 cout<<"调用默认构造函数"<<endl; 8 } 9 Student(string name):Name(name){ 10 cout<<"调用带参数的构造函数"<<endl; 11 } 12 void* operator new(size_t size){ //重载operator new 13 cout<<"调用了operator new"<<endl; 14 return malloc(size); 15 } 16 void* operator new[](size_t size){ //重载operator new[] 17 cout<<"调用operator new[]"<<endl; 18 return malloc(size); 19 } 20 private: 21 string Name; 22 }; 23 24 int main(){ 25 Student *stu=new Student[5]; 26 delete []stu; 27 return 0; 28 }
4.可以像调用其他普通函数一样,手动的调用operator new,例如:
1 void* name=operator new(sizeof(string));
这里的operator new将返回指针,指向一块足够容纳一个string对像的内存
• placement new
从本质上来讲,placement new是对operator new的一个重载,它的声明如下:
void* operator new(size_t size,void* ptr);
特别注意:
1.与operator new不同,placement new定义在头文件"new.h"中,并且相比与operator new多接受一个void* 型指针参数ptr,但它也只是简单地将指针ptr返回。
placement new在头文件new.h中的源代码:
2.placement new可以在其参数指针ptr所指地址上构建一个对象(通过调用其构造函数)从而实现定位构造,也就是说placement new可以在取得一块可以容纳指定类型的对象的内存后,在这块内存上手动构造该类型的对象。而new operator的第二步(调用构造函数)通常就是通过placement new来实现的。
3.placement new的调用语法:
语法1:new(指针) 类() //调用默认构造函数 语法2:new(指针) 类(参数) //调用带参数的构造函数
示例:
1 #include<iostream> 2 #include<string> 3 #include<new> 4 using namespace std; 5 class Student{ 6 public: 7 Student(){ 8 cout<<"调用默认构造函数"<<endl; 9 } 10 Student(string name):Name(name){ 11 cout<<"调用带参数的构造函数"<<endl; 12 } 13 private: 14 string Name; 15 }; 16 17 void* operator new(size_t size){ //重载operator new 18 cout<<"调用了operator new"<<endl; 19 return malloc(size); 20 } 21 int main(){ 22 //Student *stu1=new Studdent; 23 Student *stu1=(Student*)::operator new(sizeof(Student)); 24 new(stu1) Student(); 25 delete stu1; 26 cout<<"---------分界线--------------"<<endl; 27 //Student *stu2=new Student("Tomwenxing"); 28 Student *stu2=(Student*)::operator new(sizeof(Student)); 29 new(stu2) Student("Tomwenxing"); 30 delete stu2; 31 return 0; 32 }
4.placement new虽然实现了在指定内存地址上用指定类型的构造函数来构造一个对象的功能,但除非特别必要,不要直接使用placement new,因为这毕竟不是用来构造对象的正式写法,而只不过是new operator的一个步骤而已,当我们使用new operator时,编译器会相应地自动生成调用placement new的代码,而不需要我们手动对其进行编写。
二、delete的浅析
在C++中,delete的使用和new的使用是相对应
• delete operator
delete operator,又称delete表达式(delete expression) ,它和new operator相对应,主要功能是用来释放动态开辟的内存空间的。
语法:
语法1:delete 指针名 //释放指针所指的动态开辟的内存空间(存变量) 语法2:delete []指针名 //释放指针所指的动态开辟的内存空间(存数组)
示例:
string *name=new string("Tomwenxing"); delete name;//语法1的演示 int *data=new int[5]; delete []data;//语法2的演示
事实上和new operator相似,delete operator在释放动态开辟的内存空间时,主要完成了两项工作:
1.调用析构函数,释放动态空间中存储的内容
2.释放动态空间(由operator delete来完成,很多编译其借助C语言中的free来实现)
1 #include<iostream> 2 #include<string> 3 #include<new> 4 using namespace std; 5 class Student{ 6 public: 7 Student(){ 8 cout<<"调用默认构造函数"<<endl; 9 } 10 Student(string name){ 11 cout<<"调用带参数的构造函数"<<endl; 12 Name=new string; 13 *Name=name; 14 } 15 ~Student(){ 16 cout<<"调用析构函数"<<endl; 17 delete Name; 18 } 19 private: 20 string *Name; 21 }; 22 23 int main(){ 24 Student *stu=new Student("Tomwenxing"); 25 delete stu; 26 return 0; 27 }
Question:delete operator为什么先调用析构函数然后才释放动态开辟的内存空间呢?
Answer:以上面的示例为例,当指向完语句Student *stu=new Student("Tomwenxing")之后,内存中的空间分配情况大致如下:
此时如果不先调用析构函数来释放构造函数中new operator开辟的内存空间,而是先释放main函数中new operator开辟的内存空间,则会导致内存泄漏(堆区中动态开辟的用来存储字符串“Tomwenxing”的内存空间无法释放,因为此时指向这个空间的指针Name所占据的内存空间已经被释放,系统将无法准确定位该内存空间)
• operator delete
delete operator的第二步(释放动态开辟的内存空间)是通过operator delete来实现的,它的声明通常如下:
void operator delete(void *p);
特别注意:
1.operator delete的返回值类型是void而不是void*,并且operator delete和operator delete[]均支持重载。
2.如果我们只打算处理原始的、为设置初始值的内存,这应该完全回避new operator和delete operator,改调用operator new取得内存并以operator delete释放内存
1 void *buffer=operator new(50*sizeof(char)); 2 ...... 3 operator delete(buffer);
这组行为相当于在C++中调用malloc和free
3.很多编译器是通过free来实现operator delete的